Skip to content

Commit

Permalink
feat: add SignECDSA function (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
kklash authored Apr 4, 2022
1 parent 6d0be87 commit 9e6b550
Show file tree
Hide file tree
Showing 6 changed files with 2,957 additions and 0 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ This package provides primitives for elliptic curve cryptographic operations on

This library is not finished, stable, or audited - depend on it at your own peril!

### [API Reference](https://pkg.go.dev/github.com/kklash/ekliptic)

## Elliptic-whah?

Elliptic curve cryptography is a relatively new field of [asymmetric public-key cryptography](https://cryptobook.nakov.com/asymmetric-key-ciphers). An elliptic curve is just a cubic equation of a particular form. The secp256k1 curve, for example, is `y² = x³ + 7`. To make this curve equation useful, we first define an addition operation that 'adds' two `(x, y)` points on the curve to produce a third point _also_ on the curve. From that, you can create a multiplication operation to multiply a 2D point by some 1D (scalar) number, by simply adding the point to itself many times.
Expand Down Expand Up @@ -114,6 +116,39 @@ fmt.Printf("Bob's derived secret: %x\n", bobSharedKey)
// Bob's derived secret: 375a5d26649704863562930ded2193a0569f90f4eb4e63f0fee72c4c05268feb
```

Signing a message with ECDSA.

```go
import (
cryptorand "crypto/rand"
mathrand "math/rand"
)

randReader := mathrand.New(mathrand.NewSource(1))

key, _ := cryptorand.Int(randReader, Secp256k1_CurveOrder)
nonce, _ := cryptorand.Int(randReader, Secp256k1_CurveOrder)

hashedMessage := sha256.Sum256([]byte("i love you"))
hashedMessageInt := new(big.Int).SetBytes(hashedMessage[:])

r := new(big.Int)
s := new(big.Int)

SignECDSA(
key, nonce, hashedMessageInt,
r, s,
)

fmt.Printf("r: %x\n", r)
fmt.Printf("s: %x\n", s)

// output:
//
// r: 4a821d5ec008712983929de448b8afb6c24e5a1b97367b9a65b6220d7f083fe3
// s: 2e4f380e0ea1dfcb7cced430437c98b4570a06b3e929a3b19e6bbd53df2cf3f6
```

## Hacking on Ekliptic

| Command | Usage |
Expand Down
44 changes: 44 additions & 0 deletions ecdsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package ekliptic

import "math/big"

// SignECDSA signs a message hash z using the private key d, and a random (or deterministically
// derived) nonce k. It sets r and s to the resulting signature parts.
//
// Both the nonce k and the private key d should be generated with equal probability distribution
// over the range [1, Secp256k1_CurveOrder). SignECDSA Panics if k or d is not within this range.
func SignECDSA(
d, k, z *big.Int,
r, s *big.Int,
) {
if k.Cmp(Secp256k1_CurveOrder) >= 0 || k.Cmp(one) == -1 {
panic("SignECDSA: expected nonce k to be in range [1, Secp256k1_CurveOrder)")
} else if d.Cmp(Secp256k1_CurveOrder) >= 0 || d.Cmp(one) == -1 {
panic("SignECDSA: expected private key d to be in range [1, Secp256k1_CurveOrder)")
}

// (x, _) = k * G
x := new(big.Int)
MultiplyBasePoint(k, x, new(big.Int))

// r = x mod N
r.Mod(x, Secp256k1_CurveOrder)

// m = rd + z
m := x.Mul(r, d)
m.Add(m, z)
x = nil

// s = k⁻¹ * m mod N
s.ModInverse(k, Secp256k1_CurveOrder)
s.Mul(s, m)
s.Mod(s, Secp256k1_CurveOrder)

// always provide canonical signatures.
//
// if s > (N/2):
// s = N - s
if s.Cmp(Secp256k1_CurveOrderHalf) == 1 {
s.Sub(Secp256k1_CurveOrder, s)
}
}
74 changes: 74 additions & 0 deletions ecdsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package ekliptic

import (
cryptorand "crypto/rand"
"crypto/sha256"
"fmt"
"math/big"
mathrand "math/rand"
"testing"

"github.com/kklash/ekliptic/test_vectors"
)

func TestSignECDSA(t *testing.T) {
r := new(big.Int)
s := new(big.Int)

for i, vector := range test_vectors.ECDSAVectors {
SignECDSA(
vector.PrivateKey, vector.Nonce, vector.Hash,
r, s,
)

if !equal(r, vector.R) || !equal(s, vector.S) {
t.Errorf(`invalid ECDSA signature for vector %d. Got:
r: %.64x
s: %.64x
Wanted:
r: %.64x
s: %.64x
`, i, r, s, vector.R, vector.S)
}
}
}

func BenchmarkSignECDSA(b *testing.B) {
r := new(big.Int)
s := new(big.Int)

b.ResetTimer()
for i := 0; i < b.N; i++ {
vector := test_vectors.ECDSAVectors[i%len(test_vectors.ECDSAVectors)]
SignECDSA(
vector.PrivateKey, vector.Nonce, vector.Hash,
r, s,
)
}
}

func ExampleSignECDSA() {
randReader := mathrand.New(mathrand.NewSource(1))

key, _ := cryptorand.Int(randReader, Secp256k1_CurveOrder)
nonce, _ := cryptorand.Int(randReader, Secp256k1_CurveOrder)

hashedMessage := sha256.Sum256([]byte("i love you"))
hashedMessageInt := new(big.Int).SetBytes(hashedMessage[:])

r := new(big.Int)
s := new(big.Int)

SignECDSA(
key, nonce, hashedMessageInt,
r, s,
)

fmt.Printf("r: %x\n", r)
fmt.Printf("s: %x\n", s)

// output:
//
// r: 4a821d5ec008712983929de448b8afb6c24e5a1b97367b9a65b6220d7f083fe3
// s: 2e4f380e0ea1dfcb7cced430437c98b4570a06b3e929a3b19e6bbd53df2cf3f6
}
41 changes: 41 additions & 0 deletions test_vectors/ecdsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package test_vectors

import (
_ "embed"
"encoding/json"
"math/big"
)

// ECDSAVector represents a test vector for the elliptic curve digital signature algorithm
// on a given message hash with a private key and nonce.
type ECDSAVector struct {
Hash *big.Int
PrivateKey *big.Int
Nonce *big.Int
R, S *big.Int
}

//go:embed ecdsa.json
var ecdsaJsonBytes []byte

func loadECDSAVectors() ([]*ECDSAVector, error) {
var rawJsonObjects []map[string]string

if err := json.Unmarshal(ecdsaJsonBytes, &rawJsonObjects); err != nil {
return nil, err
}

vectors := make([]*ECDSAVector, len(rawJsonObjects))

for i, obj := range rawJsonObjects {
vectors[i] = &ECDSAVector{
Hash: hexint(obj["hash"]),
PrivateKey: hexint(obj["d"]),
Nonce: hexint(obj["nonce"]),
R: hexint(obj["r"]),
S: hexint(obj["s"]),
}
}

return vectors, nil
}
Loading

0 comments on commit 9e6b550

Please sign in to comment.