Writing

Rosa signaturorumleg. cstef, 29.xi.2024

Using ECC for (Multi-)Signatures

Math-powered methods for proving message authenticity, sounds great, doesn't it?

9 min readcrypto

[!NOTE] I am not a cryptographer, nor a mathematician. This article is the result of my own research and understanding of the subject. If you find any mistakes, please let me know!

The vast majority of what is written here is taken from various sources, which are listed at the end of this article. I highly recommend you to read them if you want to dive deeper into the subject.

Elliptic curves may not write your emails, but they can help prove you sent them. Our goal is to output a signature (π‘Ÿ,𝑠) for a given message π‘š, so that the recipient can verify that the sender is authentic. The sender’s keys are (𝑝,𝑃), with 𝑝 the private key, and 𝑃 the public one.

ECDSA Signatures

Let’s take a look at the Elliptic Curve Digital Signature Algorithm (ECDSA).

  1. Compute the hash β„Ž=𝐻(π‘š) where 𝐻(π‘₯) is any cryptographic hash function (e.g. SHA-256).
  2. Generate a random π‘˜ number in in the current subgroup.
  3. Calculate the associated random point on the curve 𝐾=π‘˜β‹…πΊ, with 𝐺 a generator, with π‘₯𝐾 the π‘₯-coordinate of this point.
  4. Calculate the signature 𝑠=π‘˜βˆ’1β‹…(β„Ž+π‘₯𝐾⋅𝑝), where π‘˜βˆ’1 is the modular inverse of π‘˜.

By sending (π‘Ÿ,𝑠), you confirm you know:

  • The content of the message π‘š
  • The private key 𝑝 associated to 𝑃=𝑝⋅𝐺

The verifier may follow this procedure to check if the message π‘š that he received along with (π‘Ÿ,𝑠) is authentic:

  1. Compute the hash β„Ž=𝐻(π‘š) with the same cryptographic hash function defined before.
  2. Calculate the modular inverse of 𝑠: 𝑆=π‘ βˆ’1
  3. Recover the random point used in the signature process: 𝐾=β„Žπ‘†β‹…πΊ+π‘Ÿπ‘†β‹…π‘ƒ
  4. Check whether π‘₯𝐾=π‘Ÿ, if so, then the message is authentic.

To prove this works, let’s start with the definition of the signature 𝑠:

𝑠=π‘˜βˆ’1β‹…(β„Ž+π‘₯𝐾⋅𝑝)βŸΊπ‘ β‹…π‘˜=β„Ž+π‘₯πΎβ‹…π‘βŸΊπ‘ β‹…π‘˜=β„Ž+π‘Ÿβ‹…π‘βŸ(π‘Ž)

Incorporate (π‘Ž) into 𝐾:

𝐾=β„Žπ‘†β‹…πΊ+π‘Ÿπ‘†β‹…π‘ƒ=β„Žπ‘ βˆ’1⋅𝐺+π‘Ÿπ‘ βˆ’1β‹…(𝑝⋅𝐺)=π‘ βˆ’1(β„Žβ‹…πΊ+π‘Ÿβ‹…(𝑝⋅𝐺))=π‘ βˆ’1(β„Ž+π‘Ÿβ‹…π‘)⏟(π‘Ž)⋅𝐺=π‘ βˆ’1π‘ βŸ=1β‹…π‘˜β‹…πΊ=π‘˜β‹…πΊ

Which is just our definition of 𝐾 when generating the signature.

Cool Kids Public Key Recovery

Let’s suppose you are talking through a Tin can telephone with your friend and every byte you send matters. You don’t want to send the public key 𝑃 along with the signature (π‘Ÿ,𝑠) because that’s just too much data. Instead, you can recover the public key from the signature and the message.

Given π‘₯𝐾, there are typically two candidate points 𝐾𝑖′ that fit. And since we know that 𝐾=β„Žπ‘†β‹…πΊ+π‘Ÿπ‘†β‹…π‘ƒ, which we just verified works, it can be rearranged to isolate the public key 𝑃:

𝐾𝑖′=β„Žπ‘†β‹…πΊ+π‘Ÿπ‘†β‹…π‘ƒπ‘–βŸΊπ‘ƒπ‘–=π‘ βˆ’1(π‘ŸπΎπ‘–β€²βˆ’β„ŽπΊ)

To choose which one is the correct one, we need to verify the signature with each 𝑃𝑖:

𝐾𝑖=β„Žπ‘†β‹…πΊ+π‘Ÿπ‘†β‹…π‘ƒπ‘–=(π‘₯𝐾,𝑦𝐾)π‘₯𝐾=?π‘Ÿ

This ambiguity is often removed by adding a single bit π‘βˆˆ{0,1} into the signature message: (π‘Ÿ,𝑠,𝑏)

Schnorr Signatures

Schnorr signatures are a bit like ECDSA, but faster and simpler. We are going to use the same keys (𝑝,𝑃) as before, with 𝑝 the private key and 𝑃 the public one. We’ll first see the procedure and then discuss the mathematical proof of why this works.

  1. Sample a random nonce π‘Ÿβ†β„€π‘›
What the hell is

℀𝑛 ?

The set ℀𝑛 is a cyclic group of integers, isomorphic to the quotient group β„€/𝑛℀. It is basically just the set of integers modulo 𝑛.

℀𝑛={0,1,2,…,π‘›βˆ’1}

In our case, 𝑛 is the order (how many points are in) of the subgroup generated by 𝐺. If the curve’s cofactor β„Ž is 1, then 𝑛 is the order of the curve. If β„Ž is not 1, the order is π‘›β„Ž=ord(𝐺)

  1. Multiply it by the generator: 𝑅=π‘ŸπΊ
  2. We can now compute the challenge 𝑒=𝐻(π‘…β€–π‘ƒβ€–π‘š)
  3. And the signature: 𝑠=π‘Ÿ+𝑒𝑝

The final signature is (𝑅,𝑠).

Once the signature has been emitted, verifying it is as easy as checking:

𝑠⋅𝐺=𝑅+𝑒𝑃

To know 𝑒, the verifier needs to know the message π‘š, the public key 𝑃. In some cases, you might not need to include the public key into the challenge and simply hash 𝑒=𝐻(π‘…β€–π‘š).

Why it Works

There isn’t really a proof needed for the verifying step as it’s just factoring out 𝐺, but here you are:

𝑠⋅𝐺=𝑅+π‘’π‘ƒβŸΊπ‘ β‹…πΊ=π‘ŸπΊβŸπ‘…+β„Žπ‘πΊβŸπ‘ƒβŸΊπ‘ β‹…πΊ=(π‘Ÿ+𝑒𝑝)⋅𝐺

One trickier part is to explain why we are adding a random nonce π‘Ÿ to both the signature and the challenge. This value has to be sampled randomly and must not be reused. If it is, the private key can be recovered. Let’s first take the case where no π‘Ÿ is used:

𝑠=π‘’π‘βŸΊπ‘=π‘ βˆ’1𝑒

Recovering the private key is as simple as multiplying the challenge 𝑒 by the modular inverse of 𝑠. Not good.

Now, let’s take the case where π‘Ÿ is reused, with two messages π‘š1 and π‘š2, along with their respective signatures (𝑅,𝑠1) and (𝑅,𝑠2):

{ 𝑠1=π‘Ÿ+𝑒1π‘βŸΊπ‘Ÿ=(𝑠1βˆ’π‘’1𝑝) (π‘Ž) 𝑠2=π‘Ÿ+𝑒2π‘βŸΊπ‘Ÿ=(𝑠2βˆ’π‘’2𝑝) (𝑏)

Combining (π‘Ž) and (𝑏):

π‘Ÿ=(𝑠1βˆ’π‘’1𝑝)=(𝑠2βˆ’π‘’2𝑝)βŸΊπ‘’1π‘βˆ’π‘’2𝑝=𝑠1βˆ’π‘ 2βŸΊπ‘β‹…(𝑒1βˆ’π‘’2)=𝑠1βˆ’π‘ 2βŸΊπ‘=𝑠1βˆ’π‘ 2𝑒1βˆ’π‘’2

You may see π‘Ÿ as an additional unknown variable that is used to prevent the linear equation system from being solved, because for 𝑛 messages, you have 𝑛 equations and 𝑛+1 unknowns, which is unsolvable in our case.

Aggregating Signatures

Schnorr signatures have the nice property that they can be aggregated, this means that a group of people can sign a message π‘š together and the signature can be verified as if it was signed by a single person. Let’s consider our signing group 𝑆={π‘Ž,𝑏,𝑐} for Alice, Bob and Charlie respectively.

AliceBobCharlieπ‘…π‘Ž=π‘Ÿπ‘Žβ‹…πΊπ‘…π‘=π‘Ÿπ‘β‹…πΊπ‘…π‘=π‘Ÿπ‘β‹…πΊ

The aggregated nonce is simply the sum of all nonces:

𝑅=βˆ‘π‘–βˆˆπ‘†π‘…π‘–=π‘…π‘Ž+𝑅𝑏+𝑅𝑐=(π‘Ÿπ‘Ž+π‘Ÿπ‘+π‘Ÿπ‘)⋅𝐺

Likewise for the public key:

𝑃=βˆ‘π‘–βˆˆπ‘†π‘ƒπ‘–=π‘ƒπ‘Ž+𝑃𝑏+𝑃𝑐=(π‘π‘Ž+𝑝𝑏+𝑝𝑐)⋅𝐺

Each of them computes the challenge 𝑒 along with the final signature 𝑠𝑖 with the parameters just agreed upon:

𝑒=𝐻(π‘…β€–π‘ƒβ€–π‘š)𝑠𝑖=𝑒𝑝𝑖+π‘Ÿπ‘–

Everyone now sends their 𝑠𝑖 to the group.

And aggregate again:

𝑠=βˆ‘π‘–βˆˆπ‘†π‘ π‘–

Because we are just adding signatures parts together, we can group the nonces and the private keys in our final signature:

𝑠=βˆ‘π‘–βˆˆπ‘†π‘ π‘–=βˆ‘π‘–βˆˆπ‘†(𝑒𝑝𝑖+π‘Ÿπ‘–)=π‘’π‘π‘Ž+π‘Ÿπ‘Ž+𝑒𝑝𝑏+π‘Ÿπ‘+𝑒𝑝𝑐+π‘Ÿπ‘=π‘Ÿπ‘Ž+π‘Ÿπ‘+π‘Ÿπ‘βŸNonces+𝑒(π‘π‘Ž+𝑝𝑏+𝑝𝑐)⏟Private Keys=βˆ‘π‘–βˆˆπ‘†π‘Ÿπ‘–+π‘’β‹…βˆ‘π‘–βˆˆπ‘†π‘π‘–

In the verifying step, multiplying each side by 𝐺:

𝑠𝐺=βˆ‘π‘–βˆˆπ‘†π‘Ÿπ‘–β‹…πΊ+π‘’β‹…βˆ‘π‘–βˆˆπ‘†π‘π‘–β‹…πΊ=βˆ‘π‘–βˆˆπ‘†π‘…π‘–+π‘’β‹…βˆ‘π‘–βˆˆπ‘†π‘ƒπ‘–=𝑅+𝑒𝑃

But wait!

At no point in this procedure, we ever check if the nonces or the public keys provided are honest. What if Charlie provided a malicious key π‘ƒπ‘βˆ— in the sharing step ? Because everyone doesn’t send their public key at the exact same time, Carol could wait for everyone to send theirs, and compute:

π‘ƒπ‘βˆ—=π‘ƒπ‘βˆ’π‘ƒπ‘Žβˆ’π‘ƒπ‘

The aggregated key will look like:

𝑃=βˆ‘π‘–βˆˆπ‘†π‘ƒπ‘–=π‘ƒπ‘Ž+𝑃𝑏+π‘ƒπ‘βˆ—=π‘ƒπ‘Ž+𝑃𝑏+(π‘ƒπ‘βˆ’π‘ƒπ‘Žβˆ’π‘ƒπ‘)=𝑃𝑐

Carol just wiped everyone else from the signing key, and is now in full control of the signature. How can we prevent that ?

Multi-Signatures, don’t trust, verify

The most common way to prevent this is to force everyone to provide a proof that their public key is honest. This is done by providing a Proof of Knowledge for the private key, proving that they know the private key associated to the public key they provided.

Let’s take the case of a prover Patricia and a verifier Victor. Patricia wants to prove that she knows the private key 𝑝 associated to the public key 𝑃=𝑝⋅𝐺. The proof is done in four steps:

  1. Patricia samples π‘Ÿβ†β„€π‘› at random and sends 𝑅=π‘Ÿβ‹…πΊ to Victor.
  2. Victor sends a challenge 𝑐←℀𝑛 to Patricia.
  3. Patricia computes 𝑧=π‘Ÿ+𝑐𝑝 and sends it to Victor.
  4. Victor verifies that 𝑧⋅𝐺=𝑅+𝑐𝑃.

This works because:

𝑧=π‘Ÿ+𝑐𝑝𝑧⋅𝐺=(π‘Ÿ+𝑐𝑝)⋅𝐺=π‘Ÿβ‹…πΊ+𝑐𝑝⋅𝐺=𝑅+𝑐𝑃

But what if Patricia doesn’t know the associated private key but still wants to prove that her key is honest ? Remember commitment schemes ? We can use them here. In our case, every participant will commit to their public key before disclosing it. Think of it as putting your public key in a box, sealing it and waiting for everyone to do the same before opening it. Let’s get back to our group 𝑆={π‘Ž,𝑏,𝑐}:

  1. Each participant 𝑖 hashes their public key 𝑃𝑖 and sends 𝐻(𝑃𝑖) to everyone.
  2. Once everyone has sent their hash, they disclose their public key 𝑃𝑖.
  3. Everyone verifies that the hash they received matches the public key.
  4. The signing process continues as usual.

This way, everyone can be sure that the public keys are honest and that no one is trying to pull a fast one.

There’s more!

Random nonces are also aggregated, and at no point we are verifying that they are authentic. The exploit method is a bit trickier, I recommend you to read this article by conduition on the subject if you want to know the details.

References and Suggested readings

  • Practical Cryptography for Developers - Digital Signatures
    Svetlin Nakov
    cryptobook.nakov.com

  • A Dive Into the Math Behind Bitcoin Schnorr Signatures
    conduition.io

  • Wagner’s Birthday Attack - How to Break InsecureMuSig
    conduition.io

  • How to Prove Schnorr Assuming Schnorr: Security of Multi- and Threshold Signatures
    Elizabeth Crites, Chelsea Komlo, and Mary Maller
    eprint.iacr.org