ECDSA [1] is used in Bitcoin and Ethereum with the secp256k1 curve, and creates a signature (\(r,s\)) for a message (\(m\)). This involves creating a key pair. The private key is \(d\) and the public key is \(Q=d.G\), and where \(G\) is the base point on the curve. The private key is used to sign the message, and the public key will prove that the private key signed it. In this case we will use the a range of curves, including NIST P256 and secp256k1 [Source code].
EC Curve Keys using Bouncy Castle and C# |
Creating key pair
With our curve, we have a generator point of \(G\) and an order \(n\). We start by generating a private key (\(d\)) and then generate the public key of:
\(Q=d.G\)
The public key is a point on the curve, and where it is derived from adding the point \(G\), \(d\) times.
Signing the message
With a message (\(m\)), we aim to apply the private key, and then create a signature (\(r,s\)). First we create a random nonce value (\(k\)) and then determine the point:
\(P=k.G\)
Next we compute the x-axis point of this point:
\(r=P_x \pmod n \)
This gives us the \(r\) value of the signature. Next, we take the hash value of the message:
\(e=H(m)\)
And then compute the \(s\) value as:
\(s=k^{-1}.(e + d.r) \pmod n\)
The signature is (\(r,s\)). This can be coded with:
var signer = SignerUtilities.GetSigner(signtype); var privateKey = (ECPrivateKeyParameters) keyPair.Private; var publicKey = (ECPublicKeyParameters) keyPair.Public; signer.Init(true, privateKey); signer.BlockUpdate(System.Text.Encoding.ASCII.GetBytes(msg), 0, msg.Length);
Verifying the signature
We can verify by taking the message (\(m\), the signature (\(r,s\)) and the public key (\(Q\)):
\(e=H(m)\)
Next we compute:
\(w =s^{-1} \pmod n\)
\(u_1 = e.w\)
\(u_2 = r.w\)
We then compute the point:
\(X = u_1.G + u_2.Q \)
And then take the x-co-ordinate of \(X\):
\(x = X_x \pmod n\)
If \(x\) is equal to \(r\), the signature has been verified. This can be implemented as:
var signer2 = SignerUtilities.GetSigner(signtype); signer2.Init(false, publicKey); signer2.BlockUpdate(System.Text.Encoding.ASCII.GetBytes(msg), 0, msg.Length); var rtn=signer2.VerifySignature(signer.GenerateSignature());
Code
First we create a folder named "bc_ecdsa", and then go into that folder.We can create a Dotnet console project for .NET 8.0 with:
dotnet new console --framework net8.0
Next we can install the Bouncy Castle library with:
dotnet add package BouncyCastle.Crypto.dll --version 1.8.1
Next some code:
namespace ECDSA { using Org.BouncyCastle.Asn1.X9; using Org.BouncyCastle.Security; using Org.BouncyCastle.Crypto.Parameters; using System.Security.Cryptography; class Program { static void Main(string[] args) { var curvename="secp256k1"; var signtype="SHA1withECDSA"; var msg="Hello"; // SHA1withECDSA, SHA256withECDSA, SHA224withECDSA, SHA384withECDSA, SHA512withECDSA if (args.Length >0) msg=args[0]; if (args.Length >1) curvename=args[1]; if (args.Length >2) signtype=args[2]; try { X9ECParameters ecParams = ECNamedCurveTable.GetByName(curvename); var curveparam = new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed()); Org.BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters keygenParams = new Org.BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters (curveparam, new SecureRandom()); Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator generator = new Org.BouncyCastle.Crypto.Generators.ECKeyPairGenerator("ECDSA"); generator.Init(keygenParams); var keyPair = generator.GenerateKeyPair(); // Create signature var signer = SignerUtilities.GetSigner(signtype); var privateKey = (ECPrivateKeyParameters) keyPair.Private; var publicKey = (ECPublicKeyParameters) keyPair.Public; signer.Init(true, privateKey); signer.BlockUpdate(System.Text.Encoding.ASCII.GetBytes(msg), 0, msg.Length); // Verify signature var signer2 = SignerUtilities.GetSigner(signtype); signer2.Init(false, publicKey); signer2.BlockUpdate(System.Text.Encoding.ASCII.GetBytes(msg), 0, msg.Length); var rtn=signer2.VerifySignature(signer.GenerateSignature()); Console.WriteLine("== Curve: {0} ",curvename); Console.WriteLine("== Message: {0} ",msg); Console.WriteLine("== Signature type: {0} ",signtype); Console.WriteLine("\n== Signature === "); Console.WriteLine("== Signature: {0} [{1}] ",Convert.ToHexString(signer.GenerateSignature()),Convert.ToBase64String(signer.GenerateSignature())); Console.WriteLine("== Verified: {0} ",rtn); Console.WriteLine("\n== Private key === "); Console.WriteLine("== D ==={0} ",privateKey.D.ToString()); Console.WriteLine("\n== Public key === "); Console.WriteLine("== Q_x ==={0} ",publicKey.Q.XCoord); Console.WriteLine("== Q_t ==={0} ",publicKey.Q.YCoord); Console.WriteLine("\n\nCurve details: G={0}, N={1}, H={2}", ecParams.G, ecParams.N, ecParams.H); Console.WriteLine("A={0}\nB={1}\nField size={2}",ecParams.Curve.A,ecParams.Curve.B,ecParams.Curve.FieldSize); } catch (Exception e) { Console.WriteLine("Error: {0}",e.Message); } } } }
A sample run for secp2561 with SHA512 ECDSA gives:
== Curve: secp256k1 == Message: Hello 1234 == Signature type: SHA512withECDSA == Signature === == Signature: 30450220378FA5A081E34BF2BBB6A72B9EBD17416CDF1ADCD2801F9B2E1582068D23DDBB022100C481964EC325974516797168BC705ED1EF5D5A02B70DFE3F1338146D0AE24075 [MEUCIQCf8NGsOSdmQR89azDKLDr84zcQ1mv+oPFgSW/B4WzCgQIgOFPyfByScpD6AtxIaDsx0CREuyNyJ0NSW8oBJU2a+Ik=] == Verified: True == Private key === == D ===69492866219808339828853746543402782282399383887441314311192822336293855185015 == Public key === == Q_x ===f56de6285596136aab932f9b5f4ca78340d166ce142b93f29f4c8e32eca1f371 == Q_t ===7a515b2e8aeaa771ea5340303ceff36b6514d0b754340497fdd40452a07d0cb6 Curve details: G=(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,1,0), N=115792089237316195423570985008687907852837564279074904382605163141518161494337, H=1 A=0 B=7 Field size=256
And for NIST (FIPS) with SHA256 ECDSA with P-256:
== Curve: P-256 == Message: Hello 1234 == Signature type: SHA256withECDSA == Signature === == Signature: 3044022029F02F3C9E35502EFEC01E02247EC7C8364E59DA937E8991A99D865D1A84B9F4022023BD02DFDF2DD29DD32AD6852BB18E4CD950368167C917549EF9350FB9043582 [MEQCIFeW5UeHs3ZlMGzBMaFtWZ5E+E1+1h+iq0wdJZrk3HfEAiBBU1dGt3BMRvKmod1rRwZYAJ4uljducX7xZiiBh+2tEA==] == Verified: True == Private key === == D ===48162401781262777440380258921331045135827863188813617576547475076726166303788 == Public key === == Q_x ===262110b0f9e81546b31b1ecf5a90b7c6b78427afdd3d775b106cf296236afe38 == Q_t ===64844498520b82005ea0215e87993a6935890b65c910881a8dec375bed454f1b Curve details: G=(6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,1,ffffffff00000001000000000000000000000000fffffffffffffffffffffffc), N=115792089210356248762697446949407573529996955224135760342422259061068512044369, H=1 A=ffffffff00000001000000000000000000000000fffffffffffffffffffffffc B=5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b Field size=256
References
[1] Johnson, D., Menezes, A., & Vanstone, S. (2001). The elliptic curve digital signature algorithm (ECDSA). International journal of information security, 1(1), 36–63 [here].