-
-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ECDSA over secp256k1 signatures and verification #490
Changes from 45 commits
f43a97c
6434eba
e8540b6
41d502d
2760af4
9f31c38
5862d43
4fd34e2
a4ca057
22bd57b
4e17514
46d8f92
7164f07
890b185
50de116
931044d
fe8a8aa
bec1536
34442fa
06f7a5f
ad16403
e9174e6
4d72a6b
a8ecd59
828189c
722fa37
64130a1
e9387e8
0d24f6a
5229551
1d05da4
c0b3806
87bc887
8000567
04ce1c8
0c5195f
89688ba
5011fe3
fa5a5eb
2f6a897
2693aec
1b44a8f
c2c39af
fa9e0ab
4cac2ec
30285ff
901597e
6c09fe1
0bbc839
20057e0
9642ca6
2fd2bb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Constantine | ||
# Copyright (c) 2018-2019 Status Research & Development GmbH | ||
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy | ||
# Licensed and distributed under either of | ||
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). | ||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). | ||
# at your option. This file may not be copied, modified, or distributed except according to those terms. | ||
|
||
import | ||
constantine/zoo_exports, | ||
constantine/signatures/ecdsa, | ||
constantine/hashes/h_sha256, | ||
constantine/named/algebras, | ||
constantine/math/elliptic/[ec_shortweierstrass_affine], | ||
constantine/math/[arithmetic, ec_shortweierstrass], | ||
constantine/platforms/[abstractions, views] | ||
|
||
export NonceSampler | ||
|
||
const prefix_ffi = "ctt_ecdsa_secp256k1_" | ||
type | ||
SecretKey* {.byref, exportc: prefix_ffi & "seckey".} = object | ||
## A Secp256k1 secret key | ||
raw: Fr[Secp256k1] | ||
|
||
PublicKey* {.byref, exportc: prefix_ffi & "pubkey".} = object | ||
## A Secp256k1 public key for ECDSA signatures | ||
raw: EC_ShortW_Aff[Fp[Secp256k1], G1] | ||
|
||
Signature* {.byref, exportc: prefix_ffi & "signature".} = object | ||
## A Secp256k1 signature for ECDSA signatures | ||
r: Fr[Secp256k1] | ||
s: Fr[Secp256k1] | ||
|
||
func pubkey_is_zero*(pubkey: PublicKey): bool {.libPrefix: prefix_ffi.} = | ||
## Returns true if input is 0 | ||
bool(pubkey.raw.isNeutral()) | ||
|
||
func pubkeys_are_equal*(a, b: PublicKey): bool {.libPrefix: prefix_ffi.} = | ||
## Returns true if inputs are equal | ||
bool(a.raw == b.raw) | ||
|
||
func signatures_are_equal*(a, b: Signature): bool {.libPrefix: prefix_ffi.} = | ||
## Returns true if inputs are equal | ||
bool(a.r == b.r and a.s == b.s) | ||
|
||
proc sign*(sig: var Signature, | ||
secretKey: SecretKey, | ||
message: openArray[byte], | ||
nonceSampler: NonceSampler = nsRandom) {.libPrefix: prefix_ffi, genCharAPI.} = | ||
## Sign `message` using `secretKey` and store the signature in `sig`. The nonce | ||
## will either be randomly sampled `nsRandom` or deterministically calculated according | ||
## to RFC6979 (`nsRfc6979`) | ||
sig.coreSign(secretKey.raw, message, sha256, nonceSampler) | ||
|
||
proc verify*( | ||
publicKey: PublicKey, | ||
message: openArray[byte], | ||
signature: Signature | ||
): bool {.libPrefix: prefix_ffi, genCharAPI.} = | ||
## Verify `signature` using `publicKey` for `message`. | ||
result = publicKey.raw.coreVerify(message, signature, sha256) | ||
|
||
func derive_pubkey*(public_key: var PublicKey, secret_key: SecretKey) {.libPrefix: prefix_ffi.} = | ||
## Derive the public key matching with a secret key | ||
## | ||
## The secret_key MUST be validated | ||
public_key.raw.derivePubkey(secret_key.raw) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Constantine | ||
# Copyright (c) 2018-2019 Status Research & Development GmbH | ||
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy | ||
# Licensed and distributed under either of | ||
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). | ||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). | ||
# at your option. This file may not be copied, modified, or distributed except according to those terms. | ||
|
||
import | ||
constantine/named/algebras, | ||
constantine/math/elliptic/ec_shortweierstrass_affine, | ||
constantine/math/io/[io_fields, io_extfields] | ||
|
||
{.used.} | ||
|
||
# Generators | ||
# ----------------------------------------------------------------- | ||
# https://www.secg.org/sec2-v2.pdf page 9 (13 of PDF), sec. 2.4.1 | ||
|
||
# The group G_1 (== G) is defined on the curve Y^2 = X^3 + 7 over the field F_p | ||
# with p = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 | ||
# with generator: | ||
const Secp256k1_generator_G1* = EC_ShortW_Aff[Fp[Secp256k1], G1]( | ||
x: Fp[Secp256k1].fromHex"0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", | ||
y: Fp[Secp256k1].fromHex"0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8" | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
# Constantine | ||
# Copyright (c) 2018-2019 Status Research & Development GmbH | ||
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy | ||
# Licensed and distributed under either of | ||
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). | ||
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). | ||
# at your option. This file may not be copied, modified, or distributed except according to those terms. | ||
|
||
##[ | ||
|
||
Performs (de-)serialization of ECDSA signatures into ASN.1 DER encoded | ||
data following SEC1: | ||
|
||
https://www.secg.org/sec1-v2.pdf | ||
|
||
In contrast to `codecs_ecdsa_secp256k1.nim` this file is generic under the choice | ||
of elliptic curve. | ||
]## | ||
|
||
import | ||
constantine/named/algebras, | ||
constantine/math/io/[io_bigints, io_fields], | ||
constantine/math/elliptic/[ec_shortweierstrass_affine], | ||
constantine/platforms/[abstractions, views], | ||
constantine/serialization/codecs # for fromHex and (in the future) base64 encoding | ||
|
||
type | ||
## Helper type for ASN.1 DER signatures to avoid allocation. | ||
## Has a `data` buffer of 72 bytes (maximum possible size for | ||
## a signature for `secp256k1`) and `len` of actually used data. | ||
## `data[0 ..< len]` is the actual signature. | ||
DERSignature*[N: static int] = object | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmmh, similar to Ecc or Kzg, I would capitalize as Der but DerSignature feels wrong in German xD There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Haha, I think we can live with that. ;) We speak perfect German anyway, so no one would confuse 'DerSignature' with 'Der Signatur'. Only the latter would be wrong German after all! |
||
data*: array[N, byte] # Max size: 6 bytes overhead + 33 bytes each for r,s | ||
len*: int # Actual length used | ||
|
||
template DERSigSize*(Name: static Algebra): int = | ||
6 + 2 * (Fr[Name].bits.ceilDiv_vartime(sizeof(pointer)) + 1) | ||
|
||
proc toDER*[Name: static Algebra; N: static int](derSig: var DERSignature[N], r, s: Fr[Name]) = | ||
## Converts signature (r,s) to DER format without allocation. | ||
## Max size is 72 bytes (for Secp256k1 or any curve with 32 byte scalars in `Fr`): | ||
## 6 bytes overhead + up to 32+1 bytes each for r,s. | ||
## 6 byte 'overhead' for: | ||
## - `0x30` byte SEQUENCE designator | ||
## - total length of the array | ||
## - integer type designator `0x02` (before `r` and `s`) | ||
## - length of `r` and `s` | ||
## | ||
## Implementation follows ideas of Bitcoin's secp256k1 implementation: | ||
## https://github.com/bitcoin-core/secp256k1/blob/f79f46c70386c693ff4e7aef0b9e7923ba284e56/src/ecdsa_impl.h#L171-L193 | ||
const OctetWidth = 8 | ||
const N = Fr[Name].bits.ceilDiv_vartime(OctetWidth) # 32 for `secp256k1` | ||
|
||
template toByteArray(x: Fr[Name]): untyped = | ||
## Convert to a 33 byte array. Leading zero byte required if | ||
## first real byte (idx 1) highest bit set (> 0x80). | ||
var a: array[N+1, byte] | ||
discard toOpenArray[byte](a, 1, N).marshal(x.toBig(), bigEndian) | ||
a | ||
|
||
# 1. Prepare the data & determine required sizes | ||
|
||
# Convert r,s to big-endian bytes | ||
var rBytes = r.toByteArray() | ||
var sBytes = s.toByteArray() | ||
var rLen = N + 1 | ||
var sLen = N + 1 | ||
|
||
# Skip leading zeros but ensure high bit constraint | ||
var rPos = 0 | ||
while rLen > 1 and rBytes[rPos] == 0 and (rBytes[rPos+1] < 0x80.byte): | ||
dec rLen | ||
inc rPos | ||
var sPos = 0 | ||
while sLen > 1 and sBytes[sPos] == 0 and (sBytes[sPos+1] < 0x80.byte): | ||
dec sLen | ||
inc sPos | ||
|
||
# Set total length | ||
derSig.len = 6 + rLen + sLen | ||
|
||
|
||
# 2. Write the actual data | ||
var pos = 0 | ||
template setInc(val: byte): untyped = | ||
# Set `val` at `pos` and increase `pos` | ||
derSig.data[pos] = val | ||
inc pos | ||
|
||
# Write DER structure, global | ||
setInc 0x30 # sequence | ||
setInc (4 + rLen + sLen).byte # total length | ||
|
||
# `r` prefix | ||
setInc 0x02 # integer | ||
setInc rLen.byte # length of `r` | ||
# Write `r` bytes in valid region | ||
derSig.data.rawCopy(pos, rBytes, rPos, rLen) | ||
inc pos, rLen | ||
|
||
# `s` prefix | ||
setInc 0x02 # integer | ||
setInc sLen.byte # length of `s` | ||
# Write `s` bytes in valid region | ||
derSig.data.rawCopy(pos, sBytes, sPos, sLen) | ||
inc pos, sLen | ||
|
||
assert derSig.len == pos | ||
|
||
proc fromRawDER*(r, s: var array[32, byte], sig: openArray[byte]): bool = | ||
## Extracts the `r` and `s` values from a given DER signature. | ||
## | ||
## Returns `true` if the input is a valid DER encoded signature | ||
## for `secp256k1` (or any curve with 32 byte scalars). | ||
var pos = 0 | ||
|
||
template checkInc(val: untyped): untyped = | ||
if pos > sig.high or sig[pos] != val: | ||
# Invalid signature | ||
return false | ||
inc pos | ||
template readInc(val: untyped): untyped = | ||
if pos > sig.high: | ||
return false | ||
val = sig[pos] | ||
inc pos | ||
|
||
checkInc(0x30) # SEQUENCE | ||
var totalLen: byte; readInc(totalLen) | ||
|
||
template parseElement(el: var array[32, byte]): untyped = | ||
var eLen: byte; readInc(eLen) # len of `r` | ||
if pos + eLen.int > sig.len: # would need more data than available | ||
return false | ||
# read `r` into *last* `rLen` bytes | ||
var eStart = el.len - eLen.int | ||
if eStart < 0: # indicates prefix 0 due to first byte >= 0x80 (highest bit set) | ||
doAssert eLen == 33 | ||
inc pos # skip first byte | ||
eStart = 0 # start from 0 in `el` | ||
dec eLen # decrease eLen by 1 | ||
el.rawCopy(eStart, sig, pos, eLen.int) | ||
inc pos, eLen.int | ||
|
||
# `r` | ||
checkInc(0x02) # INTEGER | ||
parseElement(r) | ||
|
||
# `s` | ||
checkInc(0x02) # INTEGER | ||
parseElement(s) | ||
|
||
# NOTE: `totalLen` does not include the prefix [0x30, totalLen] 2 bytes. Hence -2. | ||
assert pos - 2 == totalLen.int, "Pos = " & $pos & ", totalLen = " & $totalLen | ||
|
||
result = true | ||
|
||
proc fromDER*(r, s: var array[32, byte], derSig: DERSignature) = | ||
## Splits a given `DERSignature` back into the `r` and `s` elements as | ||
## raw byte arrays. | ||
fromRawDER(r, s, derSig.data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's Bitcoin protocol that uses SHA256?
We can rename the file btc_ecdsa_secp256k1.