Implementing DSA and ECDSA in Crystal
Github repository: https://github.com/ajgara/crystal-primitives
Introduction
Let's explore how we implemented the Digital Signature Algorithm (DSA) in Crystal. The code is available here This includes the implementation of finite fields with their operations and the specific steps to sign a message using DSA.
Crystal is a high-level programming language. It is precompiled and has a garbage collector. Its speed is comparable to Rust and C.
DSA, Digital Signature Algorithm, is an algorithm used for digitally signing a file and authenticating its owner. It is the algorithm that Bitcoin uses to allow wallet owners to authorize transactions.
ECDSA, Elliptic Curve Digital Signature Algorithm, is an advanced digital signature algorithm that uses the principles of elliptic curve cryptography. It is known for its enhanced security and efficiency in comparison to traditional digital signature algorithms like DSA. ECDSA is widely used in various security protocols and systems, including SSL/TLS for secure web communications and in many cryptocurrencies. This algorithm allows for shorter key lengths while maintaining high security, making it a popular choice for modern cryptographic applications.
Crystal does not have developed libraries for cryptographic primitives, so this small work is a first approach in that direction.
Finite Fields in Crystal
To start, we will tackle the implementation of finite fields in Crystal, including their basic operations. This lays the necessary foundation for the development of the DSA algorithm.
Implementing finite fields means creating a Finite Field Element object and implementing field operations on it.
The operations worth mentioning are:
Calculating the Inverse of an Element
This is done using the extended Euclidean algorithm. If is a finite field of order , , then, using the extended Euclidean algorithm, we can find and such that
Since , is the inverse of in .
Calculating the Square Root
The method for calculating the square root of an element in depends on the prime . If , the square root of an element is . Otherwise, the Tonelli-Shanks algorithm must be used.
Implementation
The first important decision to make is where to store the prime that determines the order of the field. The approach suggested in the book "Programming Bitcoin" is to specify the prime every time you create a FiniteFieldElement. In Sagemath, you first create the Field by assigning the prime and then use it to create your elements. Here, we opt for the simplest approach of all, which is to hardcode the prime into the field. We will use . It just so happens that , so it is easy to calculate square roots in .
We use BigInt to store the class representatives of the field. We implement all operations modulo .
Summary of the DSA Algorithm
We will provide a brief summary of the DSA algorithm, highlighting its key components and how they interact to provide secure digital signatures.
DSA is an algorithm for the owner of a private key to sign a message (actually the hash of a message) in such a way that another person can verify the signature using only the signer's public key.
There are some parameters that are public:
- The prime
- A prime such that for some
- A randomly chosen value , usually
The signer has a private key whose public key is To sign the hash of a message , the signer:
-
Chooses randomly and calculates .
-
If , start again with another .
-
Calculate
-
The signature is the pair
The verifier receives the signature and verifies it in the following way:
- Calculates .
- Calculates .
- Calculates .
- Calculates .
- The signature is valid if .
I recommend doing the calculations on paper.
If someone wanted to forge the signature, that is, give a signature for the public key without knowing , then they would need to find a value such that is a solution of , which is essentially solving the discrete logarithm.
Challenges of Implementing DSA in Crystal
We implemented a publicValues class where we store the public parameters: , , , , .
We modeled the signer in a Signer class, which upon initialization generates a private key and derives the public key that can be requested via a method. It has a sign method that signs the hash of a message and returns the signature .
We did not model the Verifier; instead, we implemented a function that takes the message and the signature and performs the calculations to verify it, returning True or False.
The main challenge is a consequence of the decisions we made regarding the prime when implementing finite fields. We had hardcoded the prime as a variable of the class. However, when we want to implement DSA, we are going to make calculations modulo .
Attention to technical details is crucial. It's important to know when you're operating in , when you're in , and when you're in .
I chose to take a shortcut to get everything up and running more or less quickly. We work in when we are in , then if I need to do the calculations modulo , I switch everything to Int and do the calculations modulo . It worked.
Elliptic Curves
We modeled EllipticCurvePoint. Many of the decisions that need to be made when modeling the finite field reappear.
We hardcoded the curve parameters and in the point class. If later we want to generalize to any curve, we will see.
We can create a point on the curve from a pair of BigInt, and the method to create the point verifies if the point is on the curve.
I also implemented a class method to create a curve point from only an , to work more comfortably.
For the neutral element, we added an is_infinity attribute that can be True or False and a class method to create the infinity point.
We implemented equality, addition, and also scalar multiplication.
There is a detail about scalar multiplication. I was tempted to multiply by elements of the field but you can multiply by any integer.
Challenges of Implementing ECDSA
As before, I implemented an ECDSAPublicValues class that holds the public values involved in the signature, which are the generator of the subgroup of the curve, and its order . I also found it convenient to add the prime of the finite field, understanding that it is repeated.
I implemented an ECDSASigner class for signing and a separate function for verification.
When creating a signer, I have to pass a random number generator rng that it uses to create a private key , and then a public key .
To sign a message , I need its hash . Then I randomly draw an element from , obtain , and keep , the first coordinate of , looking at it in . I calculate in and return the signature .
The verifier takes the signature and calculates and . The signature is valid if the first coordinate of is equal to . The difficulty I had was that everything has to be calculated in and not in , since the periodicity of the subgroup generated by and of the field are different.
Next Steps
There were many untidiness in the code. Many times I used integers modulo instead of creating elements of a finite field . This could be quite tidied up. One issue is that the cryptographic protocols themselves do not clarify much where each element lives in each step. Sometimes they start in , then you think of it as an integer, then in . Sometimes they do not even clarify the existence of , only thinking of it as integers modulo .
This is something we could think about and ground.
There is also the option to continue with the book "Programming Bitcoin". And also refactor everything to be able to do calculations with more generic fields and curves.