Threshold ECDSA Using GG20

The thing I love about research is that moment you find a new paper, and it changes your viewpoint on things. And so this is the paper I’ve…

Photo by Kelly Sikkema on Unsplash

Threshold ECDSA Using GG20

The thing I love about research is that moment you find a new paper, and it changes your viewpoint on things. And so this is the paper I’ve been reading, and it has given me so many ideas in creating distributed environments for multiparty computation [here]:

The focus of the paper is to create an ECDSA signature using secret shares of a private key. In this way we can create an ECDSA signature using multiple Shamir shares, and where the private is never actually revealed but split over two or more parties.

So, let’s use the Coinbase Kryptology library to implement a 2-from-2 threshold ECDSA scheme using GG20 (Gennaro and Goldfeder, 2020). Initially, we will use the secp256k1 curve and then generate a new private key (ikm):

k256 := btcec.S256()
ikm, _ := dealer.NewSecret(k256)

This private key will not be stored on the parties which receive the shares and can be deleted after the shares have been distributed. We can then generate the associated public key (pk) and split the key into a number of shares (sharesMap):

pk, sharesMap, _ := dealer.NewDealerShares(k256, tshare, nshare, ikm)
fmt.Printf("Message: %s\n", msg)
fmt.Printf("Sharing scheme: Any %d from %d\n", tshare, nshare)
fmt.Printf("Random secret: (%x)\n\n", ikm)
fmt.Printf("Public key: (%s %s)\n\n", pk.X, pk.Y)

This public key will be used to check that the signature that the parties create. We also create public shares which can be used to verify the shares created:

pubSharesMap, _ := dealer.PreparePublicShares(sharesMap)

We then create Paillier keys which will be used to distribute the key signing:

keyPrimesArray := genPrimesArray(2)
paillier1, _ := paillier.NewSecretKey(keyPrimesArray[0].p, keyPrimesArray[0].q)

paillier2, _ := paillier.NewSecretKey(keyPrimesArray[1].p, keyPrimesArray[1].q)

dealerSetup := &signingSetup{
curve: k256,
pk: pk,
sharesMap: sharesMap,
pubSharesMap: pubSharesMap,
pubkeys: map[uint32]*paillier.PublicKey{
1: &paillier1.PublicKey,
2: &paillier2.PublicKey,
},
privkeys: map[uint32]*paillier.SecretKey{
1: paillier1,
2: paillier2,
},
proofParams: &dealer.TrustedDealerKeyGenType{
ProofParams: dealerParams,
},
}

The dealer of the shares is now set up. What we need now is two parties to receive the shares, and for them to sign for a message (msg). We can create a hash of the message with:

m := []byte(msg)
msgHash := sha256.Sum256(m)

Next, we will create two participants (p1 and p2), and where p1 gets the first set of shares (sharesMap1), and the ID for these, and p2 gets the second set of shares (sharesMap2), and the required ID for these:

p1 := Participant{*setup.sharesMap[1], setup.privkeys[1]}
p2 := Participant{*setup.sharesMap[2], setup.privkeys[2]}

And then set them up to split the share. These will be run of either of the parties:

s1, _ := p1.PrepareToSign(setup.pk,k256Verifier,setup.curve,setup.proofParams,setup.pubSharesMap,setup.pubkeys)

s2, _ := p2.PrepareToSign(setup.pk,k256Verifier,setup.curve,setup.proofParams,setup.pubSharesMap,
setup.pubkeys)

We then go into a number of rounds, and where s1 is run on one party, and s2 on another party:

r1_s1_bcast, r1_s1_p2p, _ := s1.SignRound1()

r1_s2_bcast, r1_s2_p2p, _ := s2.SignRound1()

r2_s1_p2p, _ := s1.SignRound2(map[uint32]*Round1Bcast{2: r1_s2_bcast},map[uint32]*Round1P2PSend{2: r1_s2_p2p[1]},)

r2_s2_p2p, _ := s2.SignRound2(map[uint32]*Round1Bcast{1: r1_s1_bcast},map[uint32]*Round1P2PSend{1: r1_s1_p2p[2]},)


r3_s1_bcast, _ := s1.SignRound3(map[uint32]*P2PSend{2: r2_s2_p2p[1]},)

r3_s2_bcast, _ := s2.SignRound3(map[uint32]*P2PSend{1: r2_s1_p2p[2]},)

r4_s1_bcast, _ := s1.SignRound4(map[uint32]*Round3Bcast{2: r3_s2_bcast})

r4_s2_bcast, _ := s2.SignRound4(map[uint32]*Round3Bcast{1: r3_s1_bcast})

r5_s1_bcast, r5_s1_p2p, _ := s1.SignRound5(map[uint32]*Round4Bcast{2: r4_s2_bcast},)
r5_s2_bcast, r5_s2_p2p, _ := s2.SignRound5(map[uint32]*Round4Bcast{1: r4_s1_bcast},)

r6_s1_bcast, _ := s1.SignRound6Full(msgHash, map[uint32]*Round5Bcast{2: r5_s2_bcast},map[uint32]*Round5P2PSend{2: r5_s2_p2p[1]},)
r6_s2_bcast, _ := s2.SignRound6Full(msgHash, map[uint32]*Round5Bcast{1: r5_s1_bcast}, map[uint32]*Round5P2PSend{1: r5_s1_p2p[2]},)

Now each party can generate the shared signature:

s1_sig, _ := s1.SignOutput(
map[uint32]*Round6FullBcast{2: r6_s2_bcast},
)

s2_sig, _ := s2.SignOutput(
map[uint32]*Round6FullBcast{1: r6_s1_bcast},
)

We can then check the signature that has been generated against the public key:

publicKey := ecdsa.PublicKey{
Curve: ecc.P256k1(), //secp256k1
X: setup.pk.X,
Y: setup.pk.Y,
}

fmt.Printf("Message: %s\n", msg)
fmt.Printf("Node 1 signature: (%d %d)\n", s1_sig.R, s1_sig.S)

fmt.Printf("Node 2 signature: (%d %d)\n", s2_sig.R, s2_sig.S)

rtn := ecdsa.Verify(&publicKey, msgHash, s1_sig.R, s1_sig.S)
fmt.Printf("\nSignature Verified: %v", rtn)

A sample run give [here]:

Message: hello
Sharing scheme: Any 2 from 2
Random secret: (4ba8a796f32ec45a1197de6089fa236e50801928ad24fecac8c1ab09e531a166)
Public key: (26312696491752052329024843181188922148239837748725137580407874797348180244367 15363854046934109047334192976294337901992160667810868208924200646181218876027)
Node 1 signature: (58831626654799325138549587498198878137267315963404497589755928522155537172377 52424895795438944536339814184633346175739904425137488476336337801850504274550)
Node 2 signature: (58831626654799325138549587498198878137267315963404497589755928522155537172377 52424895795438944536339814184633346175739904425137488476336337801850504274550)
Signature Verified: true

Conclusions

For resilience and security, we need infrastructure which is more distributed. In this case, we have taken a private key, and then distributed secret shares of this to two parties. They can then use the shares they have to sign a message. Here is a running demo:

https://asecuritysite.com/kryptology/sss_gg02

References

[1] Gennaro, R., & Goldfeder, S. (2020). One Round Threshold ECDSA with Identifiable Abort. IACR Cryptol. ePrint Arch., 2020, 540.

Asecuritysite is provided free and without adverts. If you want to support its development and get access to this blog, subscribe here: