Skip to content
96 changes: 96 additions & 0 deletions docs/cryptography/tecdsa.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
:toc: macro

= T-ECDSA Protocol

toc::[]

== Setup

=== Master Public Key
_Master Publick Key_ is a value needed for commitment generation. This value is generated individually for each signer before each of two phases: key generation and signing.

. Each signer generates _Master Public Key Share_ and broadcasts it to other signers in _Master Public Key Share Message_.
+
.Master Public Key Share Message
[halign=center,options="header"]
|===
^|name ^|type ^|description

^|`signerID`
^|`string`
^|Signer's ID

^|`masterPublicKeyShare`
^|`[]byte`
^|Master Public Key Share
|===
. Each signer combines _Master Public Key Shares_ of all signers to get _Master Public Key_.

== Key Generation

=== Round #1

. Each signer generates _DSA Key Share_ which consists of _Private Key Share_ and _Public Key Share_.
+
_Private Key Share_ value must be kept secret and never shared.
+
_Public Key Share_ value cannot be exposed until all signers in the group commit to their values.

. Each signer calculates a commitment for the _Public Key Share_ value and broadcasts this commitment in
_Public Key Share Commitment Message_.

.Public Key Share Commitment Message
[halign=center,options="header"]
|===
^|name ^|type ^|description

^|`signerID`
^|`string`
^|Signer's ID

^|`publicKeyShareCommitment`
^|`commitment.MultiTrapdoorCommitment`
^|Commitment to _Public Key Share_
|===

=== Round #2
After commitments from all signers are gathered the next round starts.

. Signers reveal their _DSA Key Shares_ and broadcast _Key Share Reveal Message_.
+
Since _Private Key Share_ should always be kept secret it is first encrypted with Paillier and this encrypted value is broadcasted along with
Zero Knowledge Proof Π~i~ to confirm that _Private Key Share_ value is in [-q^3^, q^3^] range.
+
_Public Key Share_ is broadcasted with a decommitment key used in a previous round in commitment to _Public Key Share_ value.
+
.Key Share Reveal Message
[halign=center,options="header"]
|===
^|name ^|type ^|description

^|`signerID`
^|`string`
^|Signer's ID

^|`secretKeyShare`
^|`paillier.Cypher`
^|Encrypted _Private Key Share_

^|`publicKeyShare`
^|`curve.Point`
^|_Public Key Share_

^|`publicKeyShareDecommitmentKey`
^|`commitment.DecommitmentKey`
^|Decommitment key for _Public Key Share_

^|`secretKeyProof`
^|`zkp.DsaPaillierKeyRangeProof`
^|ZKP Π~i~ -- _Private Key Share_ is in range [-q^3^, q^3^]
|===

. Each signer validates received _Key Share Reveal Messages_ and combines shares to get encrypted _Private Key_ and _Public Key_ which forms _DSA Key_.

== Signing

TBD
8 changes: 7 additions & 1 deletion pkg/tecdsa/signer_keygen.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func (ls *LocalSigner) CombineDsaKeyShares(

if len(revealedShares) != ls.signerGroup.InitialGroupSize {
return nil, fmt.Errorf(
"all group members should reveal shares; Got %v, expected %v",
"all group members should reveal shares; got %v, expected %v",
len(revealedShares),
ls.signerGroup.InitialGroupSize,
)
Expand All @@ -241,6 +241,10 @@ func (ls *LocalSigner) CombineDsaKeyShares(
if commitmentMsg.signerID == revealedSharesMsg.signerID {
foundMatchingRevealMessage = true

if !ls.signerGroup.Contains(commitmentMsg.signerID) {
return nil, fmt.Errorf("signer with ID %s is not in signers group", commitmentMsg.signerID)
}

if revealedSharesMsg.isValid(
ls.commitmentMasterPublicKey,
commitmentMsg.publicKeyShareCommitment,
Expand All @@ -249,12 +253,14 @@ func (ls *LocalSigner) CombineDsaKeyShares(
secretKeyShares[i] = revealedSharesMsg.secretKeyShare
publicKeyShares[i] = revealedSharesMsg.publicKeyShare
} else {
ls.signerGroup.RemoveSignerID(commitmentMsg.signerID)
return nil, errors.New("KeyShareRevealMessage rejected")
}
}
}

if !foundMatchingRevealMessage {
ls.signerGroup.RemoveSignerID(commitmentMsg.signerID)
return nil, fmt.Errorf(
"no matching share reveal message for signer with ID=%v",
commitmentMsg.signerID,
Expand Down
21 changes: 17 additions & 4 deletions pkg/tecdsa/signer_keygen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func TestCombineWithNotEnoughRevealMessages(t *testing.T) {
}

expectedError := fmt.Errorf(
"all group members should reveal shares; Got 1, expected 10",
"all group members should reveal shares; got 1, expected 10",
)

_, err = group[0].CombineDsaKeyShares(
Expand Down Expand Up @@ -274,19 +274,32 @@ func readTestParameters() (
// createNewLocalGroup creates a new group of `LocalSigner`s that did not
// started initialization process yet.
func createNewLocalGroup() ([]*LocalSigner, *PublicParameters, error) {
paillierKeys, signerParameters, zkpParameters, signerGroup, err := readTestParameters()
paillierKeys, publicParameters, zkpParameters, groupParameters, err := readTestParameters()
if err != nil {
return nil, nil, err
}

localSigners := make([]*LocalSigner, len(paillierKeys))
for i := 0; i < len(localSigners); i++ {
localSigners[i] = NewLocalSigner(
&paillierKeys[i], signerParameters, zkpParameters, signerGroup,
&paillierKeys[i],
publicParameters,
zkpParameters,
&signerGroup{
InitialGroupSize: groupParameters.InitialGroupSize,
Threshold: groupParameters.Threshold,
},
)
}

return localSigners, signerParameters, nil
// Register signers' IDs
for i := 0; i < len(localSigners); i++ {
for j := 0; j < len(localSigners); j++ {
localSigners[i].signerGroup.AddSignerID(localSigners[j].ID)
}
}

return localSigners, publicParameters, nil
}

// initializeNewLocalGroupWithKeyShares creates and initializes a new group of
Expand Down
8 changes: 8 additions & 0 deletions pkg/tecdsa/signer_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ func (sc *signerCore) CombineMasterPublicKeyShares(
)
}

for _, message := range masterPublicKeySharesMessages[1:] {
if !sc.signerGroup.Contains(message.signerID) {
return nil, fmt.Errorf("signer with ID %s is not in signers group",
message.signerID,
)
}
}

masterPublicKey := new(bn256.G2)
masterPublicKey.Unmarshal(
masterPublicKeySharesMessages[0].masterPublicKeyShare,
Expand Down
65 changes: 43 additions & 22 deletions pkg/tecdsa/signer_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,21 @@ func (s *Round2Signer) CombineRound2Messages(
secretKeyMultiple *paillier.Cypher,
err error,
) {
groupSize := s.signerGroup.InitialGroupSize
groupSize := s.signerGroup.SignerCount()

if len(round1Messages) != groupSize {
if len(round1Messages) < s.signerGroup.Threshold {
return nil, nil, fmt.Errorf(
"round 1 messages required from all group members; got %v, expected %v",
"round 1 messages required from at least %v group members but got %v",
s.signerGroup.Threshold,
len(round1Messages),
groupSize,
)
}

if len(round2Messages) != groupSize {
if len(round2Messages) < s.signerGroup.Threshold {
return nil, nil, fmt.Errorf(
"round 2 messages required from all group members; got %v, expected %v",
"round 2 messages required from at least %v group members but got %v",
s.signerGroup.Threshold,
len(round2Messages),
groupSize,
)
}

Expand All @@ -196,6 +196,10 @@ func (s *Round2Signer) CombineRound2Messages(
if round1Message.signerID == round2Message.signerID {
foundMatchingRound2Message = true

if !s.signerGroup.Contains(round1Message.signerID) {
return nil, nil, fmt.Errorf("signer with ID %s is not in signers group", round1Message.signerID)
}

if round2Message.isValid(
s.commitmentMasterPublicKey,
round1Message.secretKeyFactorShareCommitment,
Expand All @@ -205,12 +209,14 @@ func (s *Round2Signer) CombineRound2Messages(
secretKeyFactorShares[i] = round2Message.secretKeyFactorShare
secretKeyMultipleShares[i] = round2Message.secretKeyMultipleShare
} else {
s.signerGroup.RemoveSignerID(round1Message.signerID)
return nil, nil, errors.New("round 2 message rejected")
}
}
}

if !foundMatchingRound2Message {
s.signerGroup.RemoveSignerID(round1Message.signerID)
return nil, nil, fmt.Errorf(
"no matching round 2 message for signer with ID = %v",
round1Message.signerID,
Expand Down Expand Up @@ -436,21 +442,21 @@ func (s *Round4Signer) CombineRound4Messages(
signatureFactorPublic *curve.Point, // R
err error,
) {
groupSize := s.signerGroup.InitialGroupSize
groupSize := s.signerGroup.SignerCount()

if len(round3Messages) != groupSize {
if len(round3Messages) < s.signerGroup.Threshold {
return nil, nil, fmt.Errorf(
"round 3 messages required from all group members; got %v, expected %v",
"round 3 messages required from at least %v group members but got %v",
s.signerGroup.Threshold,
len(round3Messages),
groupSize,
)
}

if len(round4Messages) != groupSize {
if len(round4Messages) < s.signerGroup.Threshold {
return nil, nil, fmt.Errorf(
"round 4 messages required from all group members; got %v, expected %v",
"round 4 messages required from at least %v group members but got %v",
s.signerGroup.Threshold,
len(round4Messages),
groupSize,
)
}

Expand All @@ -464,6 +470,10 @@ func (s *Round4Signer) CombineRound4Messages(
if round3Message.signerID == round4Message.signerID {
foundMatchingRound4Message = true

if !s.signerGroup.Contains(round3Message.signerID) {
return nil, nil, fmt.Errorf("signer with ID %s is not in signers group", round3Message.signerID)
}

if round4Message.isValid(
s.commitmentMasterPublicKey,
round3Message.signatureFactorShareCommitment,
Expand All @@ -473,12 +483,14 @@ func (s *Round4Signer) CombineRound4Messages(
signatureFactorPublicShares[i] = round4Message.signatureFactorPublicShare
signatureUnmaskShares[i] = round4Message.signatureUnmaskShare
} else {
s.signerGroup.RemoveSignerID(round3Message.signerID)
return nil, nil, errors.New("round 4 message rejected")
}
}
}

if !foundMatchingRound4Message {
s.signerGroup.RemoveSignerID(round3Message.signerID)
return nil, nil, fmt.Errorf(
"no matching round 4 message for signer with ID = %v",
round3Message.signerID,
Expand Down Expand Up @@ -570,18 +582,22 @@ func (s *Round5Signer) CombineRound5Messages(
signatureUnmask *big.Int, // TDec(w)
err error,
) {
groupSize := s.signerGroup.InitialGroupSize
groupSize := s.signerGroup.SignerCount()

if len(round5Messages) != groupSize {
if len(round5Messages) < s.signerGroup.Threshold {
return nil, fmt.Errorf(
"round 5 messages required from all group members; got %v, expected %v",
"round 5 messages required from at least %v group members but got %v",
s.signerGroup.Threshold,
len(round5Messages),
groupSize,
)
}

partialDecryptions := make([]*paillier.PartialDecryption, groupSize)
for i, round5Message := range round5Messages {
if !s.signerGroup.Contains(round5Message.signerID) {
return nil, fmt.Errorf("signer with ID %s is not in signers group", round5Message.signerID)
}

partialDecryptions[i] = round5Message.signatureUnmaskPartialDecryption
}

Expand Down Expand Up @@ -631,6 +647,7 @@ func (s *Round5Signer) SignRound6(
)

return &SignRound6Message{
signerID: s.ID,
signaturePartialDecryption: s.paillierKey.Decrypt(signatureCypher.C),
}, nil
}
Expand All @@ -647,18 +664,22 @@ type Signature struct {
func (s *Round5Signer) CombineRound6Messages(
round6Messages []*SignRound6Message,
) (*Signature, error) {
groupSize := s.signerGroup.InitialGroupSize
groupSize := s.signerGroup.SignerCount()

if len(round6Messages) != groupSize {
if len(round6Messages) < s.signerGroup.Threshold {
return nil, fmt.Errorf(
"round 6 messages required from all group members; got %v, expected %v",
"round 6 messages required from at least %v group members but got %v",
s.signerGroup.Threshold,
len(round6Messages),
groupSize,
)
}

partialDecryptions := make([]*paillier.PartialDecryption, groupSize)
for i, round6Message := range round6Messages {
if !s.signerGroup.Contains(round6Message.signerID) {
return nil, fmt.Errorf("signer with ID %s is not in signers group", round6Message.signerID)
}

partialDecryptions[i] = round6Message.signaturePartialDecryption
}

Expand Down
Loading