diff --git a/go.mod b/go.mod index e3691ee77..180c9c10c 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,6 @@ go 1.17 require ( github.com/bwesterb/go-ristretto v1.2.2 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 ) diff --git a/go.sum b/go.sum index d4e2277d5..7a6c012ed 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/math/polynomial/polynomial.go b/math/polynomial/polynomial.go index bee812742..e929e57fc 100644 --- a/math/polynomial/polynomial.go +++ b/math/polynomial/polynomial.go @@ -53,6 +53,14 @@ func (p Polynomial) Evaluate(x group.Scalar) group.Scalar { return px } +func (p Polynomial) Coefficients() []group.Scalar { + c := make([]group.Scalar, len(p.c)) + for i := range p.c { + c[i] = p.c[i].Copy() + } + return c +} + // LagrangePolynomial stores a Lagrange polynomial over the set of scalars of a group. type LagrangePolynomial struct { // Internal representation is in Lagrange basis: diff --git a/ot/simplestOT/simplestOT.go b/ot/simplestOT/simplestOT.go new file mode 100644 index 000000000..778f893b9 --- /dev/null +++ b/ot/simplestOT/simplestOT.go @@ -0,0 +1,36 @@ +// 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 simplestOT + +import ( + "github.com/cloudflare/circl/group" +) + +// Input: myGroup, the group we operate in +// Input: m0, m1 the 2 message of the sender +// Input: choice, the bit c of the receiver +// Input: index, the index of this BaseOT +func BaseOT(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 +} diff --git a/ot/simplestOT/simplestOTLocal.go b/ot/simplestOT/simplestOTLocal.go new file mode 100644 index 000000000..a977e7fc7 --- /dev/null +++ b/ot/simplestOT/simplestOTLocal.go @@ -0,0 +1,240 @@ +package simplestOT + +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 BaseOT +// 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 BaseOT +// 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/simplestOT/simplestOTParty.go b/ot/simplestOT/simplestOTParty.go new file mode 100644 index 000000000..f41f6aac7 --- /dev/null +++ b/ot/simplestOT/simplestOTParty.go @@ -0,0 +1,29 @@ +package simplestOT + +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/ot/simplestOT/simplestOT_test.go b/ot/simplestOT/simplestOT_test.go new file mode 100644 index 000000000..8868f8f8f --- /dev/null +++ b/ot/simplestOT/simplestOT_test.go @@ -0,0 +1,185 @@ +package simplestOT + +import ( + "bytes" + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" +) + +const TestBaseOTCount = 100 + +func testNegativeBaseOT(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) + rand.Read(m0) + rand.Read(m1) + + // 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("BaseOT 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 testBaseOT(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) + rand.Read(m0) + rand.Read(m1) + err := BaseOT(myGroup, &sender, &receiver, m0, m1, choice, 0) + if err != nil { + t.Error("BaseOT failed", err) + } + //Confirm + 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 benchmarBaseOT(b *testing.B, myGroup group.Group) { + var sender SenderSimOT + var receiver ReceiverSimOT + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + rand.Read(m0) + rand.Read(m1) + + for iter := 0; iter < b.N; iter++ { + err := BaseOT(myGroup, &sender, &receiver, m0, m1, iter%2, 0) + if err != nil { + b.Error("BaseOT failed") + } + } +} + +func benchmarkBaseOTRound(b *testing.B, myGroup group.Group) { + var sender SenderSimOT + var receiver ReceiverSimOT + m0 := make([]byte, myGroup.Params().ScalarLength) + m1 := make([]byte, myGroup.Params().ScalarLength) + rand.Read(m0) + rand.Read(m1) + + 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 TestBaseOT(t *testing.T) { + + t.Run("BaseOT", func(t *testing.T) { + for i := 0; i < TestBaseOTCount; i++ { + currGroup := group.P256 + choice := i % 2 + testBaseOT(t, currGroup, choice) + } + }) + t.Run("BaseOTNegative", func(t *testing.T) { + for i := 0; i < TestBaseOTCount; i++ { + currGroup := group.P256 + choice := i % 2 + testNegativeBaseOT(t, currGroup, choice) + } + }) + +} + +func BenchmarkBaseOT(b *testing.B) { + currGroup := group.P256 + benchmarBaseOT(b, currGroup) +} + +func BenchmarkBaseOTRound(b *testing.B) { + currGroup := group.P256 + benchmarkBaseOTRound(b, currGroup) +} diff --git a/secretsharing/ss.go b/secretsharing/ss.go new file mode 100644 index 000000000..f7d4b75f8 --- /dev/null +++ b/secretsharing/ss.go @@ -0,0 +1,171 @@ +// Package secretsharing provides methods to split secrets in shares. +// +// A (t,n) secret sharing allows to split a secret into n shares, such that the +// secret can be only recovered given more than t shares. +// +// The New function creates a Shamir secret sharing [1], which relies on +// Lagrange polynomial interpolation. +// +// The NewVerifiable function creates a Feldman secret sharing [2], which +// extends Shamir's by allowing to verify that a share corresponds to the +// secret. +// +// References +// [1] https://dl.acm.org/doi/10.1145/359168.359176 +// [2] https://ieeexplore.ieee.org/document/4568297 +package secretsharing + +import ( + "errors" + "fmt" + "io" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/math/polynomial" +) + +// Share represents a share of a secret. +type Share struct { + ID uint + Share group.Scalar +} + +// SecretSharing implements a (t,n) Shamir's secret sharing. +type SecretSharing interface { + // Params returns the t and n parameters of the secret sharing. + Params() (t, n uint) + // Shard splits the secret into n shares. + Shard(rnd io.Reader, secret group.Scalar) []Share + // Recover returns the secret provided more than t shares are given. + Recover(shares []Share) (secret group.Scalar, err error) +} + +type ss struct { + g group.Group + t, n uint +} + +// New returns a struct implementing SecretSharing interface. A (t,n) secret +// sharing allows to split a secret into n shares, such that the secret can be +// only recovered given more than t shares. It panics if 0 < t <= n does not +// hold. +func New(g group.Group, t, n uint) (ss, error) { + if !(0 < t && t <= n) { + return ss{}, errors.New("secretsharing: bad parameters") + } + s := ss{g: g, t: t, n: n} + var _ SecretSharing = s // checking at compile-time + return s, nil +} + +func (s ss) Params() (t, n uint) { return s.t, s.n } + +func (s ss) polyFromSecret(rnd io.Reader, secret group.Scalar) (p polynomial.Polynomial) { + c := make([]group.Scalar, s.t+1) + for i := range c { + c[i] = s.g.RandomScalar(rnd) + } + c[0].Set(secret) + return polynomial.New(c) +} + +func (s ss) generateShares(poly polynomial.Polynomial) []Share { + shares := make([]Share, s.n) + x := s.g.NewScalar() + for i := range shares { + id := i + 1 + x.SetUint64(uint64(id)) + shares[i].ID = uint(id) + shares[i].Share = poly.Evaluate(x) + } + + return shares +} + +func (s ss) Shard(rnd io.Reader, secret group.Scalar) []Share { + return s.generateShares(s.polyFromSecret(rnd, secret)) +} + +func (s ss) Recover(shares []Share) (group.Scalar, error) { + if l := len(shares); l <= int(s.t) { + return nil, fmt.Errorf("secretsharing: does not reach the threshold %v with %v shares", s.t, l) + } else if l > int(s.n) { + return nil, fmt.Errorf("secretsharing: %v shares above max number of shares %v", l, s.n) + } + + x := make([]group.Scalar, len(shares)) + px := make([]group.Scalar, len(shares)) + for i := range shares { + x[i] = s.g.NewScalar() + x[i].SetUint64(uint64(shares[i].ID)) + px[i] = shares[i].Share + } + + l := polynomial.NewLagrangePolynomial(x, px) + zero := s.g.NewScalar() + + return l.Evaluate(zero), nil +} + +type SharesCommitment = []group.Element + +type vss struct{ s ss } + +// SecretSharing implements a (t,n) Feldman's secret sharing. +type VerifiableSecretSharing interface { + // Params returns the t and n parameters of the secret sharing. + Params() (t, n uint) + // Shard splits the secret into n shares, and a commitment of the secret + // and the shares. + Shard(rnd io.Reader, secret group.Scalar) ([]Share, SharesCommitment) + // Recover returns the secret provided more than t shares are given. + Recover(shares []Share) (secret group.Scalar, err error) + // Verify returns true if the share corresponds to a committed secret using + // the commitment produced by Shard. + Verify(share Share, coms SharesCommitment) bool +} + +// New returns a struct implementing VerifiableSecretSharing interface. A (t,n) +// secret sharing allows to split a secret into n shares, such that the secret +// can be only recovered given more than t shares. It is possible to verify +// whether a share corresponds to a secret. It panics if 0 < t <= n does not +// hold. +func NewVerifiable(g group.Group, t, n uint) (vss, error) { + s, err := New(g, t, n) + v := vss{s} + var _ VerifiableSecretSharing = v // checking at compile-time + return v, err +} + +func (v vss) Params() (t, n uint) { return v.s.Params() } + +func (v vss) Shard(rnd io.Reader, secret group.Scalar) ([]Share, SharesCommitment) { + poly := v.s.polyFromSecret(rnd, secret) + shares := v.s.generateShares(poly) + coeffs := poly.Coefficients() + shareComs := make(SharesCommitment, len(coeffs)) + for i := range coeffs { + shareComs[i] = v.s.g.NewElement().MulGen(coeffs[i]) + } + + return shares, shareComs +} + +func (v vss) Verify(s Share, c SharesCommitment) bool { + if len(c) != int(v.s.t+1) { + return false + } + + lc := len(c) - 1 + sum := v.s.g.NewElement().Set(c[lc]) + x := v.s.g.NewScalar() + for i := lc - 1; i >= 0; i-- { + x.SetUint64(uint64(s.ID)) + sum.Mul(sum, x) + sum.Add(sum, c[i]) + } + polI := v.s.g.NewElement().MulGen(s.Share) + return polI.IsEqual(sum) +} + +func (v vss) Recover(shares []Share) (group.Scalar, error) { return v.s.Recover(shares) } diff --git a/secretsharing/ss_test.go b/secretsharing/ss_test.go new file mode 100644 index 000000000..af10d0888 --- /dev/null +++ b/secretsharing/ss_test.go @@ -0,0 +1,149 @@ +package secretsharing_test + +import ( + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/secretsharing" +) + +func TestSecretSharing(tt *testing.T) { + g := group.P256 + t := uint(3) + n := uint(5) + + s, err := secretsharing.New(g, t, n) + test.CheckNoErr(tt, err, "failed to create ShamirSS") + + want := g.RandomScalar(rand.Reader) + shares := s.Shard(rand.Reader, want) + test.CheckOk(len(shares) == int(n), "bad num shares", tt) + + tt.Run("subsetSize", func(ttt *testing.T) { + // Test any possible subset size. + for k := 0; k < int(n); k++ { + got, err := s.Recover(shares[:k]) + if k <= int(t) { + test.CheckIsErr(ttt, err, "should not recover secret") + test.CheckOk(got == nil, "not nil secret", ttt) + } else { + test.CheckNoErr(ttt, err, "should recover secret") + if !got.IsEqual(want) { + test.ReportError(ttt, got, want, t, k, n) + } + } + } + }) +} + +func TestVerifiableSecretSharing(tt *testing.T) { + g := group.P256 + t := uint(3) + n := uint(5) + + vs, err := secretsharing.NewVerifiable(g, t, n) + test.CheckNoErr(tt, err, "failed to create ShamirSS") + + want := g.RandomScalar(rand.Reader) + shares, com := vs.Shard(rand.Reader, want) + test.CheckOk(len(shares) == int(n), "bad num shares", tt) + test.CheckOk(len(com) == int(t+1), "bad num commitments", tt) + + tt.Run("verifyShares", func(ttt *testing.T) { + for i := range shares { + test.CheckOk(vs.Verify(shares[i], com) == true, "failed one share", ttt) + } + }) + + tt.Run("subsetSize", func(ttt *testing.T) { + // Test any possible subset size. + for k := 0; k < int(n); k++ { + got, err := vs.Recover(shares[:k]) + if k <= int(t) { + test.CheckIsErr(ttt, err, "should not recover secret") + test.CheckOk(got == nil, "not nil secret", ttt) + } else { + test.CheckNoErr(ttt, err, "should recover secret") + if !got.IsEqual(want) { + test.ReportError(ttt, got, want, t, k, n) + } + } + } + }) + + tt.Run("badShares", func(ttt *testing.T) { + badShares := make([]secretsharing.Share, len(shares)) + for i := range shares { + badShares[i].Share = shares[i].Share.Copy() + badShares[i].Share.SetUint64(9) + } + + for i := range badShares { + test.CheckOk(vs.Verify(badShares[i], com) == false, "verify must fail due to bad shares", ttt) + } + }) + + tt.Run("badCommitments", func(ttt *testing.T) { + badCom := make(secretsharing.SharesCommitment, len(com)) + for i := range com { + badCom[i] = com[i].Copy() + badCom[i].Dbl(badCom[i]) + } + + for i := range shares { + test.CheckOk(vs.Verify(shares[i], badCom) == false, "verify must fail due to bad commitment", ttt) + } + }) +} + +func BenchmarkSecretSharing(b *testing.B) { + g := group.P256 + t := uint(3) + n := uint(5) + + s, _ := secretsharing.New(g, t, n) + want := g.RandomScalar(rand.Reader) + shares := s.Shard(rand.Reader, want) + + b.Run("Shard", func(b *testing.B) { + for i := 0; i < b.N; i++ { + s.Shard(rand.Reader, want) + } + }) + + b.Run("Recover", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = s.Recover(shares) + } + }) +} + +func BenchmarkVerifiableSecretSharing(b *testing.B) { + g := group.P256 + t := uint(3) + n := uint(5) + + vs, _ := secretsharing.NewVerifiable(g, t, n) + want := g.RandomScalar(rand.Reader) + shares, com := vs.Shard(rand.Reader, want) + + b.Run("Shard", func(b *testing.B) { + for i := 0; i < b.N; i++ { + vs.Shard(rand.Reader, want) + } + }) + + b.Run("Recover", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = vs.Recover(shares) + } + }) + + b.Run("Verify", func(b *testing.B) { + for i := 0; i < b.N; i++ { + vs.Verify(shares[0], com) + } + }) +} diff --git a/tss/ecdsa/damgard/ecdsaLocalTSS.go b/tss/ecdsa/damgard/ecdsaLocalTSS.go new file mode 100644 index 000000000..6e04deeb6 --- /dev/null +++ b/tss/ecdsa/damgard/ecdsaLocalTSS.go @@ -0,0 +1,296 @@ +// Assumptions and Terminology +// 1. There are n parties: p_1...p_n +// 2. Every party has a label and receives a share of the secret key from the core. +// 3. Elliptic curve E(Z_p) of order q is defined as: y^2=x^3 + ax + b (mod p) +// where a, b in Z_p and Z_p is the underlying finite field for E. +// 4. We use Feldman TSS because every party needs to verify the msg from any other party. + +package ecdsaTSS + +import ( + "crypto/rand" + "errors" + + "github.com/cloudflare/circl/secretsharing" + + "github.com/cloudflare/circl/group" +) + +// Local Sign functions + +// During online round, the metals will construct their own signature share upon receiving the message +// Input: currParty, the local party +func (currParty *partySign) LocalGenSignatureShare() { + currParty.sharesig.Share.Mul(currParty.r, currParty.sharesk.Share) + currParty.sharesig.Share.Add(currParty.sharesig.Share, currParty.hashMSG) + currParty.sharesig.Share.Mul(currParty.sharesig.Share, currParty.sharekInv.Share) +} + +// Initiate local party parameters for final round of signature generation +// Input: i, this party index +// Input: currParty, the local party +// Input: preSign, the same party with preSign informations +// Input: myGroup, the group we operate in +func (currParty *partySign) LocalInit(i uint, myGroup group.Group, preSign partyPreSign) { + currParty.myGroup = preSign.myGroup + currParty.index = i + currParty.sharekInv.ID = i + currParty.sharekInv.Share = preSign.sharekInv.Share.Copy() + currParty.r = myGroup.NewScalar() + currParty.r = preSign.r.Copy() + currParty.sharesk.ID = i + currParty.sharesk.Share = myGroup.NewScalar() + currParty.sharesk.Share.SetUint64(uint64(0)) + currParty.sharesig.ID = i + currParty.sharesig.Share = myGroup.NewScalar() + currParty.sharesig.Share.SetUint64(uint64(0)) + currParty.hashMSG = myGroup.NewScalar() +} + +// Input: currParty, the local party +// Input: sssk, the share of secret key +func (currParty *partySign) Setss(sssk group.Scalar) { + currParty.sharesk.Share = sssk.Copy() +} + +func (currParty *partySign) SetMSG(hashMSG group.Scalar) { + currParty.hashMSG = hashMSG.Copy() +} + +// Local Pre computation functions + +// Initiate local party parameters for preComputation +// Input: i, this party index +// Input: n, the number of parties +// Input: currParty, the local party +// Input: myGroup, the group we operate in +func (currParty *partyPreSign) LocalInit(i, n uint, myGroup group.Group) { + currParty.index = i + currParty.myGroup = myGroup + currParty.sharek.ID = i + currParty.sharek.Share = myGroup.NewScalar() + currParty.sharek.Share.SetUint64(uint64(0)) + currParty.shareb.ID = i + currParty.shareb.Share = myGroup.NewScalar() + currParty.shareb.Share.SetUint64(uint64(0)) + currParty.sharekb.ID = i + currParty.sharekb.Share = myGroup.NewScalar() + currParty.sharekb.Share.SetUint64(uint64(0)) + currParty.sharekInv.ID = i + currParty.sharekInv.Share = myGroup.NewScalar() + currParty.sharekInv.Share.SetUint64(uint64(0)) + currParty.sharekG = myGroup.NewElement() + currParty.obfCoefks = make([][]group.Element, n) + currParty.obfCoefbs = make([][]group.Element, n) +} + +// Generate the local party information for nonce k and blinding b, +// later will be used in Feldman secret sharing to construct shares of the nonce k and k^{-1} +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +func (currParty *partyPreSign) LocalGenkb(t, n uint) { + + // first coefficient of secret polynomial k_i for this party i + currParty.polyki = currParty.myGroup.RandomNonZeroScalar(rand.Reader) + vs, err := secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + currParty.sski, currParty.obfCoefki = vs.Shard(rand.Reader, currParty.polyki) + + // secret polynomial b_i for this party i + currParty.polybi = currParty.myGroup.RandomNonZeroScalar(rand.Reader) + vs, err = secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + // the shares of polynomial b_i for every single party in the game and the obfuscated coefficient for proving correctness + currParty.ssbi, currParty.obfCoefbi = vs.Shard(rand.Reader, currParty.polybi) + + currParty.sharek = currParty.sski[currParty.index-1] + currParty.shareb = currParty.ssbi[currParty.index-1] +} + +// Update local shares of k and b +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: sharekUpdate, update for nonce k share from other party +// Input: sharebUpdate, update for blinding b share from other party +// Input: obfCoefk, the obfuscated coefficient (commitment) as for proving correctness of sharekUpdate +// Input: obfCoefb, the obfuscated coefficient (commitment) as for proving correctness of sharebUpdate +// Input: currParty, the local party +// Input: fromIndex, the index of the party who sends this update +// Output: fromIndex if Feldman check fails, otherwise 0 +func (currParty *partyPreSign) LocalUpdatekb(t, n uint, sharekUpdate, sharebUpdate secretsharing.Share, obfCoefk, obfCoefb []group.Element, fromIndex uint) uint { + currParty.sharek.Share.Add(currParty.sharek.Share, sharekUpdate.Share) + vs, err := secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + check := vs.Verify(sharekUpdate, obfCoefk) + if !check { + return fromIndex + } + + currParty.shareb.Share.Add(currParty.shareb.Share, sharebUpdate.Share) + vs, err = secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + check = vs.Verify(sharebUpdate, obfCoefb) + if !check { + return fromIndex + } + + return 0 +} + +// Compute shares for k*b as sharek*shareb +// Input: currParty, the local party +func (currParty *partyPreSign) LocalSharekb() { + currParty.sharekb.Share.Mul(currParty.sharek.Share, currParty.shareb.Share) +} + +// Compute [sharek]G +// Input: currParty, the local party +func (currParty *partyPreSign) LocalkG() { + currParty.sharekG.MulGen(currParty.sharek.Share) +} + +// Local party as a combiner collects shares for kb and computes kb^{-1} +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +// Input: shareskb, the shares for kb from other parties +// Output: kb^{-1} and possible error +func (currParty *partyPreSign) CombinerCompkbInv(t, n uint, shareskb []secretsharing.Share) (group.Scalar, error) { + s, err := secretsharing.New(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + kb, err := s.Recover(shareskb) + kbInv := currParty.myGroup.NewScalar() + kbInv.Inv(kb) + return kbInv, err +} + +// Local party as a combiner collects shares for [sharek]G and computes [k]G +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +// Input: shareskG, the shares for [k]G from other parties +// Input: indexes, the indexes for all other parties +// Output: x coordinate of [k]G and possible error +func (currParty *partyPreSign) CombinerCompkG(t, n uint, shareskG []group.Element, indexes []group.Scalar) (group.Scalar, error) { + resultkG, errkG := lagrangeInterpolatePoint(t, currParty.myGroup, shareskG, indexes) + if errkG != nil { + return nil, errkG + } + + kGBinary, errBinary := resultkG.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + + xCoor := kGBinary[1 : currParty.myGroup.Params().ScalarLength+1] + xScalar := currParty.myGroup.NewScalar() + errBinary = xScalar.UnmarshalBinary(xCoor) + if errBinary != nil { + panic(errBinary) + } + return xScalar, nil +} + +// Set the x coordinate of [k]G as r +// Input: currParty, the local party +// Input: xCoor, the x coordinate of [k]G +func (currParty *partyPreSign) Setr(xCoor group.Scalar) { + currParty.r = xCoor.Copy() +} + +// Compute share of k^{-1} as (kb)^{-1}*shareb +// Input: currParty, the local party +// Input: kbInv, the (kb)^{-1} +func (currParty *partyPreSign) LocalSharekInv(kbInv group.Scalar) { + currParty.kbInv = kbInv.Copy() + currParty.sharekInv.Share.Mul(currParty.kbInv, currParty.shareb.Share) +} + +// Helper functions + +// Check everyone receives the same coefficient for Feldman +// Input: t, the threshold parameter +// Input: obfCoefj, the obfuscated coefficient for f_i send to party j +// Input: obfCoefk, the obfuscated coefficient for f_i send to party k +// Ouput: true if obfCoefj == obfCoefk, false otherwise. +func checkObf(t uint, obfCoefj []group.Element, obfCoefk []group.Element) bool { + check := true + for i := uint(0); i < t+1; i++ { + if !(obfCoefj[i].IsEqual(obfCoefk[i])) { + check = false + } + } + return check +} + +// Lagrange Interpolation of y as element but not scalar + +// Input: myGroup, the group we operate in +// Input: targetIndex, the i +// Input: currShare, the [y_i]G +// Input: indexes, the indexes for each party +// Output: Compute a single [f_i(0)]G +func lagrangeSinglePoint(myGroup group.Group, targetIndex int, currShare group.Element, indexes []group.Scalar) group.Element { + // f_i(0) = y_i[G] + result := currShare.Copy() + + // x_i + targetLabel := (indexes)[targetIndex].Copy() + + interValue := myGroup.NewScalar() + invValue := myGroup.NewScalar() + + for k := 0; k < len(indexes); k++ { + //f_i(0) = f_i(0) * (0-x_k)/(x_i-x_k) + if k != targetIndex { + // x_k + currLabel := (indexes)[k].Copy() + + // f_i(0) * (0-x_k) + interValue.SetUint64(uint64(0)) + interValue.Sub(interValue, currLabel) + result.Mul(result, interValue) + + // (x_i-x_k) + invValue.Sub(targetLabel, currLabel) + invValue.Inv(invValue) + result.Mul(result, invValue) + + } + } + return result +} + +// Input: t, the threshold, we need at least t+1 points for Lagrange Interpolation +// Input: myGroup, the group we operate in +// Input: ss, the secret shares multiplied by generator G +// Input: indexes, the indexes for each party +// Ouput: the re-constructed secret [f(0)]G +func lagrangeInterpolatePoint(t uint, myGroup group.Group, ss []group.Element, indexes []group.Scalar) (group.Element, error) { + if uint(len(ss)) < t+1 { + return nil, errors.New("need at least t+1 points to do Lagrange Interpolation") + } + + secret := myGroup.NewElement() + for i := 0; i < len(ss); i++ { + fi := lagrangeSinglePoint(myGroup, i, ss[i], indexes) + if i == 0 { + secret.Set(fi) + } else { + secret.Add(secret, fi) + } + } + + return secret, nil +} diff --git a/tss/ecdsa/damgard/ecdsaTSS.go b/tss/ecdsa/damgard/ecdsaTSS.go new file mode 100644 index 000000000..20bcef175 --- /dev/null +++ b/tss/ecdsa/damgard/ecdsaTSS.go @@ -0,0 +1,274 @@ +// Assumptions and Terminology +// 1. There are n parties: p_1...p_n +// 2. Every party has a label and receives a share of the secret key from the core. +// 3. Elliptic curve E(Z_p) of order q is defined as: y^2=x^3 + ax + b (mod p) +// where a, b in Z_p and Z_p is the underlying finite field for E. +// 4. We use Feldman TSS because every party needs to verify the msg from any other party. + +// Background: +// ECDSA signing: Input secret key sk +// 1. Generate a random nonce k. +// 2. Compute k[G] and get its x coordinate as r. Back to step 1 if r = 0. +// 3. Compute s = k^{-1}(H(m)+sk*r). Back to step 1 if s = 0. +// 4. Signature is (r,s). + +// ECDSA Threshold Signature with Feldman secret sharing +// 1. Every party p_i has a share of the secret key, sharesk, and a share of the public key, shareskG: [sharesk]G. +// 2. Round 1: Parties use Feldman to jointly get shares, sharek, for nonce k. +// Parties use Feldman to jointly get shares, shareb, for a blinding b, which is used to derived k^{-1}. +// Party j needs to broadcast Feldman Coefficients received from Party i to all other parties and make sure everyone receives the same. +// Local: Parties compute shares, sharekb, for k*b locally. +// 3. Round 2: A combiner is responsible for collect sharekb and compute kb and (kb)^{-1} and broadcast `kb` to all parties. +// A combiner is responsible for collect [sharek]G and compute [k]G and broadcasts x coordinate of [k]G to all parties. +// All parties upon receiving (kb)^{-1}, compute (kb)^{-1}*shareb as share of k^{-1} +// 4. Online round 3: message hash arrived. +// To finish the online round for signature generation, we need: +// a. sharesk, shares for the secret key (core gave us). +// b. r, xoordinate of [k]G (Step3). +// c. hashMSG, the hash of message to be signed (Just arrived). +// d. sharekInv, shares of the k^{-1} (Step3). +// All above are ready, parties can locally compute the signature share, sharesig, as sharekInv*(hashMSG+sharesk*r) +// A combiner is responsible for collect sharesig and compute the final signature sig = (r, s) and verify it with the public key. +// +// +// +// Security: +// 1. Note this scheme has a major drawback: leaking of t+1 shares is enough to compromise the secret key., +// but 2t+1 parties are required to reconstruct the signature in step 3. +// 2. Any party fails to pass Feldman check in Round 1 should be marked as malicious. +// 3. Any party caught broadcasting different Feldman Coefficients should be marked as malicious. +// +// Limitation: Require at least 3 parties in the pre-computation phase and recontruction phase +package ecdsaTSS + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "math/big" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +// Upon receiving the secret key, f0, from customer, +// Dealer/Core generates a secret polynomial, f, for further generating party shares. +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: myGroup, the group we operate in +// Input: f0, the secret key also will be the first coefficient of polynomial f +// Output: shares of the secret key for n parties +func genSecretShare(t, n uint, myGroup group.Group, f0 group.Scalar) []secretsharing.Share { + + s, err := secretsharing.New(myGroup, t, n) + if err != nil { + panic(err) + } + + shares := s.Shard(rand.Reader, f0) + + return shares +} + +// Jointly and sequentially compute shares for the nonce k , k^{-1} as well as x coordinate of [k]G +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: myGroup, the group we operate in +// Input: parties, the parties of size n +func PreSign(t, n uint, myGroup group.Group, parties []partyPreSign) error { + // Local: Parties initiate the parameters + for i := uint(0); i < n; i++ { + parties[i].LocalInit(i+1, n, myGroup) + } + + // Local: Parties generate their local information for nonce k and blinding b + for i := uint(0); i < n; i++ { + parties[i].LocalGenkb(t, n) + } + + // Round 1 + // Parties broadcast their local information for nonce k and blinding b + // From party i to party j + for i := uint(0); i < n; i++ { + for j := uint(0); j < n; j++ { + if i != j { + errorLable := parties[j].LocalUpdatekb(t, n, parties[i].sski[j], parties[i].ssbi[j], parties[i].obfCoefki, parties[i].obfCoefbi, i) + if errorLable != 0 { + return errors.New("feldman verification failed") + } + } + parties[j].obfCoefks[i] = parties[i].obfCoefki + parties[j].obfCoefbs[i] = parties[i].obfCoefbi + } + } + + // Party j broadcast feldman coefficient received from party i to all other parties and everyone confirm they receive the same + for j := uint(0); j < n; j++ { + for i := uint(0); i < n; i++ { + // party j sends feldman coefficient received from party i to party l!=i or j + if i != j { + for l := uint(0); l < n; l++ { + if (l != i) && (l != j) { + check := checkObf(t, parties[j].obfCoefbs[i], parties[l].obfCoefbs[i]) + if !check { + return errors.New("broadcasting feldman coefficient failed") + } + check = checkObf(t, parties[j].obfCoefks[i], parties[l].obfCoefks[i]) + if !check { + return errors.New("broadcasting feldman coefficient failed") + } + } + } + } + + } + } + + // Local: Parties compute shares for k*b and [sharek]G + sskb := make([]secretsharing.Share, n) + for i := uint(0); i < n; i++ { + parties[i].LocalSharekb() + parties[i].LocalkG() + sskb[i].ID = parties[i].sharekb.ID + sskb[i].Share = parties[i].sharekb.Share.Copy() + } + + // Round 2 + // A combiner, assume party 0, combines shares for kb and compute (kb)^{-1} + if n < (2*t + 1) { + return errors.New("at least 2t+1 parties are required for computing multiplication") + } + + kbInv, err := parties[0].CombinerCompkbInv(t, n, sskb) + if err != nil { + return err + } + + // A combiner, assume party 0, combines shares for [sharek]G, compute [k]G + sskG := make([]group.Element, n) + indexes := make([]group.Scalar, n) + for i := uint(0); i < n; i++ { + sskG[i] = parties[i].sharekG + indexes[i] = myGroup.NewScalar() + indexes[i].SetUint64(uint64(parties[i].index)) + } + + xCoor, err := parties[0].CombinerCompkG(t, n, sskG, indexes) + if err != nil { + return err + } + + // Combiner informs all other party of (kb)^{-1}, and all party computes (kb)^{-1}*shareb as share of k^{-1} + // Combiner broadcasts x coordinate of [k]G + for i := uint(0); i < n; i++ { + parties[i].Setr(xCoor) + } + + for i := uint(0); i < n; i++ { + parties[i].LocalSharekInv(kbInv) + } + + return nil +} + +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 +} + +// During online round, all metals construct their own signature share upon receiving the message +// Input: n, the number of parties +// Input: myGroup, the group we operate in +func genSignatureShare(n uint, myGroup group.Group, parties []partySign) { + for i := uint(0); i < n; i++ { + parties[i].LocalGenSignatureShare() + } +} + +// ECDSA threshold signature generation +// Input: t, the threshold parameter +// Input: nPrime, the number of parties involved in the final signature generation +// Input: myGroup, the group we operate in +// Input: parties, the parties of size n +// Input: msg, the message hash +// curve: curve, the curve we operate in +// Output: signature (r,s) or possible error +func Sign(t, nPrime uint, myGroup group.Group, sharesk []secretsharing.Share, parties []partySign, preParties []partyPreSign, msg []byte, curve elliptic.Curve) (group.Scalar, group.Scalar, error) { + + // Local: Parties initiate the parameters + for i := uint(0); i < nPrime; i++ { + parties[i].LocalInit(i+1, myGroup, preParties[i]) + parties[i].Setss(sharesk[i].Share) + } + + msgBig := hashToInt(msg, curve) + msgByte := msgBig.Bytes() + + hashScalar := myGroup.NewScalar() + errBinary := hashScalar.UnmarshalBinary(msgByte) + if errBinary != nil { + panic(errBinary) + } + // Every party gets the hash Scalar + for i := uint(0); i < nPrime; i++ { + parties[i].SetMSG(hashScalar) + } + + // Parties generate signature online round 3 + genSignatureShare(nPrime, myGroup, parties) + + // A combiner interpolate the signature + sigShares := make([]secretsharing.Share, nPrime) + for i := uint(0); i < nPrime; i++ { + sigShares[i].ID = parties[i].sharesig.ID + sigShares[i].Share = parties[i].sharesig.Share.Copy() + } + s, err := secretsharing.New(parties[0].myGroup, t, nPrime) + if err != nil { + panic(err) + } + signature, err := s.Recover(sigShares) + if err != nil { + return nil, nil, err + } + return parties[0].r, signature, nil +} + +// 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, errBinary := r.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + rBig.SetBytes(rByte) + + sByte, errBinary := s.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + 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/damgard/ecdsaTSS_test.go b/tss/ecdsa/damgard/ecdsaTSS_test.go new file mode 100644 index 000000000..1d32c1514 --- /dev/null +++ b/tss/ecdsa/damgard/ecdsaTSS_test.go @@ -0,0 +1,198 @@ +package ecdsaTSS + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +const TestThreshold = 2 +const BenchThreshold = 1 +const Benchn = 3 +const BenchnPrime = 3 + +// Generate ECDSA key +func genKey(curve elliptic.Curve) (*ecdsa.PrivateKey, *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) + } + return privateKey, publicKey + +} + +func core(t, n uint, prv *ecdsa.PrivateKey, myGroup group.Group, parties []partySign, curve elliptic.Curve) []secretsharing.Share { + + // Convert the ECDSA secret key bigint into a Scalar + secretByte := prv.D.Bytes() + secretScalar := myGroup.NewScalar() + errBinary := secretScalar.UnmarshalBinary(secretByte) + if errBinary != nil { + panic(errBinary) + } + + // Core distribute shares of secret key + sharesk := genSecretShare(t, n, myGroup, secretScalar) + return sharesk +} + +func testECDSAThresholdSingle(t, n, nPrime uint, myGroup group.Group, curve elliptic.Curve, prv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) error { + + // Construct parties + parties := make([]partyPreSign, n) + + // PreSign: Precomputation + errPreSign := PreSign(t, n, myGroup, parties) + + if errPreSign != nil { + return errPreSign + } + + // Sign the message + + // Construct parties for signing + partiesSign := make([]partySign, n) + + // Core generates secret shares for every party + sharesk := core(t, n, prv, myGroup, partiesSign, curve) + + msg := []byte("Cloudflare: meow meow") + r, s, errSign := Sign(t, nPrime, myGroup, sharesk, partiesSign, parties, msg, curve) + if errSign != nil { + return errSign + } + + // Verify the signature + errVerify := Verify(r, s, msg, pub) + + if errVerify != nil { + return errVerify + } + + return nil +} + +func testECDSAThreshold(t *testing.T, threshold, n, nPrime uint, myGroup group.Group, curve elliptic.Curve) { + prv, pub := genKey(curve) + err := testECDSAThresholdSingle(threshold, n, nPrime, myGroup, curve, prv, pub) + if n < 2*threshold+1 { + if err == nil { + t.Error("Less than 2t+1 parties should fail") + } + } else { + if nPrime < 2*threshold+1 { + if err == nil { + t.Error("Signature generation should fail with less than 2t+1 parties") + } + } else { + if err != nil { + t.Error("Signature generation fail") + } + } + } +} + +func benchECDSAThreshold(b *testing.B, myGroup group.Group, curve elliptic.Curve) { + + prv, pub := genKey(curve) + + // Construct parties + parties := make([]partyPreSign, Benchn) + + // Bench PreSign + b.Run(curve.Params().Name+"-PreSign", func(b *testing.B) { + for i := 0; i < b.N; i++ { + errPreSign := PreSign(BenchThreshold, Benchn, myGroup, parties) + if errPreSign != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + } + }) + + // PreSign: Precomputation + errPreSign := PreSign(BenchThreshold, Benchn, myGroup, parties) + + if errPreSign != nil { + b.Error("Bench ECDSA TSS Precomputation FAIL!") + } + + // Construct parties for signing + partiesSign := make([]partySign, Benchn) + // Core generates secret shares for every party + sharesk := core(BenchThreshold, Benchn, prv, myGroup, partiesSign, curve) + msg := []byte("Cloudflare: meow meow") + + // Bench Sign + b.Run(curve.Params().Name+"-Sign", func(b *testing.B) { + for i := 0; i < b.N; i++ { + + r, s, errSign := Sign(BenchThreshold, BenchnPrime, myGroup, sharesk, partiesSign, parties, msg, curve) + if errSign != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + + errVerify := Verify(r, s, msg, pub) + if errVerify != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + } + }) + +} + +func TestECDSAThreshold(t *testing.T) { + for threshold := uint(1); threshold <= TestThreshold; threshold++ { + for n := threshold + 1; n < 3*threshold+1; n++ { + for nPrime := threshold + 1; nPrime <= n; nPrime++ { + + t.Run("ECDSATSS256", func(t *testing.T) { + pubkeyCurve := elliptic.P256() + curr_group := group.P256 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + t.Run("ECDSATSS384", func(t *testing.T) { + pubkeyCurve := elliptic.P384() + curr_group := group.P384 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + + t.Run("ECDSATSS521", func(t *testing.T) { + pubkeyCurve := elliptic.P521() + curr_group := group.P521 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + } + } + } +} + +func BenchmarkECDSASign256(b *testing.B) { + pubkeyCurve := elliptic.P256() + curr_group := group.P256 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + + pubkeyCurve = elliptic.P384() + curr_group = group.P384 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + + pubkeyCurve = elliptic.P521() + curr_group = group.P521 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + +} diff --git a/tss/ecdsa/damgard/party.go b/tss/ecdsa/damgard/party.go new file mode 100644 index 000000000..8e2300af6 --- /dev/null +++ b/tss/ecdsa/damgard/party.go @@ -0,0 +1,49 @@ +package ecdsaTSS + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +// Party struct for Presign +type partyPreSign struct { + myGroup group.Group + index uint // index starting from 1...n. + + // nonce k + polyki group.Scalar // first coefficient of secret polynomial k_i for this party i + sski []secretsharing.Share // the shares of polynomial k_i for every single party + obfCoefki []group.Element // obfuscated coefficient for proving correctness of sski + sharek secretsharing.Share // the final share of nonce k + sharekInv secretsharing.Share // the final share of nonce k inverse + sharekG group.Element // the final share of [k]G + obfCoefks [][]group.Element // obfuscated coefficient received from other parties + + // blinding b + polybi group.Scalar // first coefficient of secret polynomial b_i for this party i + ssbi []secretsharing.Share // the shares of polynomial b_i for every single party + obfCoefbi []group.Element // obfuscated coefficient for proving correctness of ssbi + shareb secretsharing.Share // the final share of blinding b + obfCoefbs [][]group.Element // obfuscated coefficient received from other parties + + // k * b + sharekb secretsharing.Share // the final share of k*b + kbInv group.Scalar // the inverse of k*b + + // r + r group.Scalar // the x coordinate of [k]G +} + +type partySign struct { + myGroup group.Group + + index uint // index starting from 1...n. + sharesk secretsharing.Share // The share of secret key, s, for this party, generated by the server + sharekInv secretsharing.Share // the final share of nonce k inverse + sharesig secretsharing.Share // the final signature share + + // r + r group.Scalar // the x coordinate of [k]G + // message hash + hashMSG group.Scalar +} diff --git a/tss/ecdsa/ot/Fmul/Fmul.go b/tss/ecdsa/ot/Fmul/Fmul.go new file mode 100644 index 000000000..265d3caa8 --- /dev/null +++ b/tss/ecdsa/ot/Fmul/Fmul.go @@ -0,0 +1,47 @@ +// 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 ( + "github.com/cloudflare/circl/group" +) + +// Input: myGroup, the group we operate in +// Input: securityParameter +// Output: The number of BaseOT needed +func DecideNumOT(myGroup group.Group, sp int) int { + numBaseOT := int(myGroup.Params().ScalarLength*8) + sp + return numBaseOT +} + +// Input: aInput, bInput, the private input from both sender and receiver +// Input: myGroup, the group we operate in +// Input: n, the total number of BaseOT +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 +} diff --git a/tss/ecdsa/ot/Fmul/FmulLocal.go b/tss/ecdsa/ot/Fmul/FmulLocal.go new file mode 100644 index 000000000..59cbddf24 --- /dev/null +++ b/tss/ecdsa/ot/Fmul/FmulLocal.go @@ -0,0 +1,235 @@ +package Fmul + +import ( + "crypto/rand" + "math/big" + "sync" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/ot/simplestOT" + "golang.org/x/sync/errgroup" +) + +// ---- Sender Initialization ---- + +// Input: myGroup, the group we operate in +// Input: a, the sender private input +// Input: n, the total number of BaseOT +// Output: Array of A=[ai]G for n BaseOT +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.baseOTsenders = make([]simplestOT.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 Base OT Sender Initialization + var BaseOTSender simplestOT.SenderSimOT + BaseOTSender.InitSender(myGroup, sender.m0s[index], sender.m1s[index], index) + sender.baseOTsenders[index] = BaseOTSender + }(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.baseOTsenders[i].A.Copy() + } + return As + +} + +// ---- Round1: Sender sends As to receiver ---- + +// Receiver randomly generates n choice bits, either 0 or 1 for BaseOT, 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 BaseOT +// 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.baseOTreceivers = make([]simplestOT.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.baseOTreceivers[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.baseOTreceivers[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 BaseOT +// 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.baseOTsenders[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.baseOTsenders[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 BaseOT +// 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.baseOTreceivers[index].Round3Receiver(e0s[index], e1s[index], receiver.ts[index]) + if errDec != nil { + return errDec + } + mc := receiver.baseOTreceivers[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 BaseOT +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/ot/Fmul/FmulParty.go b/tss/ecdsa/ot/Fmul/FmulParty.go new file mode 100644 index 000000000..4a09d38f9 --- /dev/null +++ b/tss/ecdsa/ot/Fmul/FmulParty.go @@ -0,0 +1,28 @@ +package Fmul + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/ot/simplestOT" +) + +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 + baseOTsenders []simplestOT.SenderSimOT // The n senders for n baseOT + 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 transfered messages from the sender + vs []group.Scalar // The n random of the receiver such that v*t = b + sigma group.Scalar // The blinding scalar + baseOTreceivers []simplestOT.ReceiverSimOT // The n receivers for n baseOT + s2 group.Scalar // The final additive share + myGroup group.Group // The elliptic curve we operate in +} diff --git a/tss/ecdsa/ot/Fmul/Fmul_test.go b/tss/ecdsa/ot/Fmul/Fmul_test.go new file mode 100644 index 000000000..44df8313d --- /dev/null +++ b/tss/ecdsa/ot/Fmul/Fmul_test.go @@ -0,0 +1,187 @@ +package Fmul + +import ( + "crypto/rand" + "math/big" + "testing" + + "github.com/cloudflare/circl/group" +) + +const TestFmulCount = 50 + +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])) + for i := 0; i < int(len(e0s[randomIndex])); i++ { + savee0[i] = e0s[randomIndex][i] + } + 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/tss/ecdsa/ot/ecdsaLocalTSSOT.go b/tss/ecdsa/ot/ecdsaLocalTSSOT.go new file mode 100644 index 000000000..b5e8a2b3e --- /dev/null +++ b/tss/ecdsa/ot/ecdsaLocalTSSOT.go @@ -0,0 +1,412 @@ +package ECDSAOT + +import ( + "crypto/rand" + "errors" + + "github.com/cloudflare/circl/tss/ecdsa/ot/Fmul" + zkRDL "github.com/cloudflare/circl/zk/dl" + + "github.com/cloudflare/circl/group" + "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 + V, r := zkRDL.ProveGen(myGroup, alice.DB, alice.R, alice.kA, aliceLabel, bobLabel) + + 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 + verify := zkRDL.Verify(bob.myGroup, bob.DB, bob.R, V, r, aliceLabel, bobLabel) + 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/ot/ecdsaTSSOT.go b/tss/ecdsa/ot/ecdsaTSSOT.go new file mode 100644 index 000000000..45686ee44 --- /dev/null +++ b/tss/ecdsa/ot/ecdsaTSSOT.go @@ -0,0 +1,136 @@ +// Reference: https://eprint.iacr.org/2018/499.pdf +// 2 out of 2 party threhsold signature scheme +// Figure 1 and Protocol 1 and 2 + +package ECDSAOT + +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 +} + +// 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 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/ot/ecdsaTSSOTParty.go b/tss/ecdsa/ot/ecdsaTSSOTParty.go new file mode 100644 index 000000000..30f11d982 --- /dev/null +++ b/tss/ecdsa/ot/ecdsaTSSOTParty.go @@ -0,0 +1,69 @@ +package ECDSAOT + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/tss/ecdsa/ot/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/ot/ecdsaTSSOT_test.go b/tss/ecdsa/ot/ecdsaTSSOT_test.go new file mode 100644 index 000000000..e89e18c31 --- /dev/null +++ b/tss/ecdsa/ot/ecdsaTSSOT_test.go @@ -0,0 +1,246 @@ +package ECDSAOT + +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 +} + +func testECDSAOT(t *testing.T, myGroup group.Group, curve elliptic.Curve) { + var Alice Alice + var Bob Bob + + // Precomputation + var alice AlicePre + var bob BobPre + // Set alice and bob label + alice.label = []byte("alice") + bob.label = []byte("bob") + errPre := Precomputation(myGroup, &alice, &bob, &Alice, &Bob) + 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) + Alice.SetKeyShare(share1) + Bob.SetKeyShare(share2) + + // Online signature generation + hash := []byte("Cloudflare: meow meow") + signature := SigGen(myGroup, &Alice, &Bob, hash, curve) + + // Verify the signature + errVerify := Verify(Alice.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 Alice Alice + var Bob Bob + + // Precomputation + var alice AlicePre + var bob BobPre + // Set alice and bob label + alice.label = []byte("alice") + bob.label = []byte("bob") + + b.Run(curve.Params().Name+"-PreInit", func(b *testing.B) { + for i := 0; i < b.N; i++ { + bob.BobInit(myGroup) + } + }) + + DB, bAs, kBInvAs := bob.BobInit(myGroup) + + b.Run(curve.Params().Name+"-PreRound1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + alice.AliceRound1(myGroup, DB, bAs, kBInvAs, alice.label, bob.label) + } + }) + + V, r, RPrime, aBs, kAInvBs := alice.AliceRound1(myGroup, DB, bAs, kBInvAs, alice.label, bob.label) + + b.Run(curve.Params().Name+"-PreRound2", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _, _, err := bob.BobRound2(V, r, RPrime, aBs, kAInvBs, alice.label, bob.label) + if err != nil { + b.Error("PreRound2 zk verification fail") + } + } + }) + + e0b, e1b, e0kBInv, e1kBInv, err := bob.BobRound2(V, r, RPrime, aBs, kAInvBs, alice.label, bob.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 := alice.AliceRound3(e0b, e1b, e0kBInv, e1kBInv) + if err != nil { + b.Error("PreRound3 decryption fail") + } + } + }) + + sigmaa, vsa, sigmakAInv, vskAInv, err := alice.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++ { + bob.BobRound4(sigmaa, sigmakAInv, vsa, vskAInv) + } + }) + + bob.BobRound4(sigmaa, sigmakAInv, vsa, vskAInv) + + Alice.SetParamters(&alice) + Bob.SetParamters(&bob) + + // Generate key shares + prvScalar, pub := genKey(myGroup, curve) + share1, share2 := KeyShareGen(myGroup, prvScalar) + Alice.SetKeyShare(share1) + Bob.SetKeyShare(share2) + + // Online signature generation + hash := []byte("Cloudflare: meow meow") + signature := SigGen(myGroup, &Alice, &Bob, hash, curve) + + // Verify the signature + errVerify := Verify(Alice.Rx, signature, hash, pub) + if errVerify != nil { + b.Error("Signature verification fail") + } + +} + +func benchECDSAOTSign(b *testing.B, myGroup group.Group, curve elliptic.Curve) { + var Alice Alice + var Bob Bob + + // Precomputation + var alice AlicePre + var bob BobPre + // Set alice and bob label + alice.label = []byte("alice") + bob.label = []byte("bob") + err := Precomputation(myGroup, &alice, &bob, &Alice, &Bob) + if err != nil { + b.Error("Precomputation fail") + } + + // Generate key shares + prvScalar, pub := genKey(myGroup, curve) + share1, share2 := KeyShareGen(myGroup, prvScalar) + Alice.SetKeyShare(share1) + Bob.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++ { + Alice.SigGenInit() + Bob.SigGenInit() + } + }) + + beaverAlice := Alice.SigGenInit() + beaverBob := Bob.SigGenInit() + + b.Run(curve.Params().Name+"-SignRound1", func(b *testing.B) { + for i := 0; i < b.N; i++ { + Alice.SigGenRound1(beaverBob, hashScalar) + Bob.SigGenRound1(beaverAlice, hashScalar) + } + }) + + sigAlice := Alice.SigGenRound1(beaverBob, hashScalar) + sigBob := Bob.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(Alice.Rx, signature, hash, pub) + if errVerify != nil { + b.Error("Signature verification fail") + } + +} + +func BenchmarkECDSAOTPRE(b *testing.B) { + pubkeyCurve := elliptic.P256() + curr_group := group.P256 + benchECDSAOTPRE(b, curr_group, pubkeyCurve) + +} + +func BenchmarkECDSAOTSign(b *testing.B) { + pubkeyCurve := elliptic.P256() + curr_group := group.P256 + benchECDSAOTSign(b, curr_group, pubkeyCurve) +} diff --git a/zk/dl/zkRDL.go b/zk/dl/zkRDL.go new file mode 100644 index 000000000..1b4e32f96 --- /dev/null +++ b/zk/dl/zkRDL.go @@ -0,0 +1,89 @@ +// 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 zkRDL + +import ( + "crypto/rand" + + "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 []byte) (group.Element, group.Scalar) { + + v := myGroup.RandomNonZeroScalar(rand.Reader) + 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...) + + dst := "zeroknowledge" + c := myGroup.HashToScalar(hashByte, []byte(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 []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...) + + dst := "zeroknowledge" + c := myGroup.HashToScalar(hashByte, []byte(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/zkRDL_test.go b/zk/dl/zkRDL_test.go new file mode 100644 index 000000000..4716981d7 --- /dev/null +++ b/zk/dl/zkRDL_test.go @@ -0,0 +1,59 @@ +package zkRDL + +import ( + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/group" +) + +const TestzkRDLCount = 10 + +func testzkRDL(t *testing.T, myGroup group.Group) { + kA := myGroup.RandomNonZeroScalar(rand.Reader) + DB := myGroup.RandomElement(rand.Reader) + + R := myGroup.NewElement() + R.Mul(DB, kA) + + V, r := ProveGen(myGroup, DB, R, kA, []byte("Prover"), []byte("Verifier")) + + verify := Verify(myGroup, DB, R, V, r, []byte("Prover"), []byte("Verifier")) + if verify == false { + t.Error("zkRDL verification failed") + } + +} + +func testzkRDLNegative(t *testing.T, myGroup group.Group) { + kA := myGroup.RandomNonZeroScalar(rand.Reader) + DB := myGroup.RandomElement(rand.Reader) + + R := myGroup.RandomElement(rand.Reader) + + V, r := ProveGen(myGroup, DB, R, kA, []byte("Prover"), []byte("Verifier")) + + verify := Verify(myGroup, DB, R, V, r, []byte("Prover"), []byte("Verifier")) + if verify == true { + t.Error("zkRDL verification should fail") + } + +} + +func TestZKRDL(t *testing.T) { + + t.Run("zkRDL", func(t *testing.T) { + for i := 0; i < TestzkRDLCount; i++ { + currGroup := group.P256 + testzkRDL(t, currGroup) + } + }) + + t.Run("zkRDLNegative", func(t *testing.T) { + for i := 0; i < TestzkRDLCount; i++ { + currGroup := group.P256 + testzkRDLNegative(t, currGroup) + } + }) + +}