mirror of https://github.com/status-im/nim-eth.git
Moved eth-keys to eth
This commit is contained in:
parent
1d6434883e
commit
4c89f9e270
17
eth.nimble
17
eth.nimble
|
@ -4,12 +4,21 @@ description = "Ethereum Common library"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
skipDirs = @["tests"]
|
skipDirs = @["tests"]
|
||||||
|
|
||||||
requires "nim > 0.18.0",
|
requires "nim > 0.19.0",
|
||||||
"nimcrypto",
|
"nimcrypto",
|
||||||
"ranges",
|
"ranges",
|
||||||
"stint",
|
"stint",
|
||||||
"byteutils"
|
"byteutils",
|
||||||
|
"secp256k1"
|
||||||
|
|
||||||
|
proc test(filename: string) =
|
||||||
|
echo "Running: ", filename
|
||||||
|
exec "nim c -r " & filename
|
||||||
|
|
||||||
|
import strutils, os
|
||||||
|
|
||||||
task test, "run tests":
|
task test, "run tests":
|
||||||
cd "tests"
|
for i in walkDirRec("tests"):
|
||||||
exec "nim c -r test_common"
|
let fn = splitPath(i).tail
|
||||||
|
if fn.startsWith("test_") and fn.endsWith(".nim"):
|
||||||
|
test(i)
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
#
|
||||||
|
# Nim Ethereum Keys (nim-eth-keys)
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# - Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# - MIT license (LICENSE-MIT)
|
||||||
|
#
|
||||||
|
|
||||||
|
import nimcrypto/hash, nimcrypto/keccak
|
||||||
|
|
||||||
|
type
|
||||||
|
EthKeysStatus* = enum
|
||||||
|
Success, ## Operation was successful
|
||||||
|
Error ## Operation failed
|
||||||
|
|
||||||
|
EthKeysException* = object of Exception
|
||||||
|
## Exception generated by this module
|
||||||
|
|
||||||
|
when not defined(native):
|
||||||
|
include eth/keys/libsecp256k1
|
||||||
|
|
||||||
|
proc ekErrorMsg*(): string {.inline.} =
|
||||||
|
## Return current error message.
|
||||||
|
result = libsecp256k1ErrorMsg()
|
||||||
|
|
||||||
|
proc isZeroKey*(seckey: PrivateKey): bool =
|
||||||
|
## Check if private key `seckey` contains only 0 bytes.
|
||||||
|
result = true
|
||||||
|
for i in seckey.data:
|
||||||
|
if i != byte(0):
|
||||||
|
result = false
|
||||||
|
|
||||||
|
proc isZeroKey*(pubkey: PublicKey): bool =
|
||||||
|
## Check if public key `pubkey` contains only 0 bytes.
|
||||||
|
result = true
|
||||||
|
for i in pubkey.data:
|
||||||
|
if i != byte(0):
|
||||||
|
result = false
|
||||||
|
|
||||||
|
proc signMessage*(seckey: PrivateKey,
|
||||||
|
data: openarray[byte]): Signature {.inline.} =
|
||||||
|
## Sign message of arbitrary length `data` using private key `seckey`.
|
||||||
|
let hash = keccak256.digest(data)
|
||||||
|
if signRawMessage(hash.data, seckey, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, ekErrorMsg())
|
||||||
|
|
||||||
|
proc signMessage*(seckey: PrivateKey, data: string): Signature {.inline.} =
|
||||||
|
## Sign message of arbitrary length `data` using private key `seckey`.
|
||||||
|
signMessage(seckey, cast[seq[byte]](data))
|
||||||
|
|
||||||
|
proc signMessage*(seckey: PrivateKey,
|
||||||
|
hash: MDigest[256]): Signature {.inline.} =
|
||||||
|
## Sign 256bit `hash` using private key `seckey`.
|
||||||
|
result = signMessage(seckey, hash.data)
|
||||||
|
|
||||||
|
proc verifyMessage*(data: openarray[byte], message: openarray[byte]): bool =
|
||||||
|
## Verify binary data blob `data` has properly signed message `message`.
|
||||||
|
var pubkey: PublicKey
|
||||||
|
if recoverSignatureKey(data, message, pubkey) == EthKeysStatus.Success:
|
||||||
|
result = true
|
||||||
|
else:
|
||||||
|
result = false
|
||||||
|
|
||||||
|
proc verifyMessage*(data: openarray[byte],
|
||||||
|
hash: MDigest[256]): bool {.inline.} =
|
||||||
|
## Verify binary data blob `data` has properly signed hash `hash`.
|
||||||
|
result = verifyMessage(data, hash.data)
|
||||||
|
|
||||||
|
proc recoverKeyFromMessage*(data: openarray[byte],
|
||||||
|
hash: MDigest[256]): PublicKey {.inline.} =
|
||||||
|
## Recover public key from signed binary blob `data` using 256bit hash `hash`.
|
||||||
|
if recoverSignatureKey(data, hash.data, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, ekErrorMsg())
|
||||||
|
|
||||||
|
proc recoverKeyFromMessage*(data: openarray[byte],
|
||||||
|
message: string): PublicKey {.inline.} =
|
||||||
|
## Recover public key from signed binary blob `data` using `message`.
|
||||||
|
var hash = keccak256.digest(message)
|
||||||
|
if recoverSignatureKey(data, hash.data, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, ekErrorMsg())
|
||||||
|
|
||||||
|
proc recoverKeyFromSignature*(signature: Signature,
|
||||||
|
message: string): PublicKey {.inline.} =
|
||||||
|
## Recover public key from signature `signature` using `message`.
|
||||||
|
var hash = keccak256.digest(message)
|
||||||
|
if recoverSignatureKey(signature, hash.data, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, ekErrorMsg())
|
||||||
|
|
||||||
|
proc recoverKeyFromSignature*(signature: Signature,
|
||||||
|
hash: MDigest[256]): PublicKey {.inline.} =
|
||||||
|
## Recover public key from signature `signature` using `message`.
|
||||||
|
if recoverSignatureKey(signature, hash.data, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, ekErrorMsg())
|
||||||
|
|
||||||
|
proc toAddress*(pubkey: PublicKey, with0x = true): string =
|
||||||
|
## Convert public key to hexadecimal string address.
|
||||||
|
var hash = keccak256.digest(pubkey.getRaw())
|
||||||
|
result = if with0x: "0x" else: ""
|
||||||
|
result.add(toHex(toOpenArray(hash.data, 12, len(hash.data) - 1), true))
|
||||||
|
|
||||||
|
proc toChecksumAddress*(pubkey: PublicKey, with0x = true): string =
|
||||||
|
## Convert public key to checksumable mixed-case address (EIP-55).
|
||||||
|
result = if with0x: "0x" else: ""
|
||||||
|
var hash1 = keccak256.digest(pubkey.getRaw())
|
||||||
|
var hhash1 = toHex(toOpenArray(hash1.data, 12, len(hash1.data) - 1), true)
|
||||||
|
var hash2 = keccak256.digest(hhash1)
|
||||||
|
var hhash2 = toHex(hash2.data, true)
|
||||||
|
for i in 0..<len(hhash1):
|
||||||
|
if hhash2[i] >= '0' and hhash2[i] <= '7':
|
||||||
|
result.add(hhash1[i])
|
||||||
|
else:
|
||||||
|
if hhash1[i] >= '0' and hhash1[i] <= '9':
|
||||||
|
result.add(hhash1[i])
|
||||||
|
else:
|
||||||
|
let ch = chr(ord(hhash1[i]) - ord('a') + ord('A'))
|
||||||
|
result.add(ch)
|
||||||
|
|
||||||
|
proc validateChecksumAddress*(a: string): bool =
|
||||||
|
## Validate checksumable mixed-case address (EIP-55).
|
||||||
|
var address = ""
|
||||||
|
var check = "0x"
|
||||||
|
if len(a) != 42:
|
||||||
|
return false
|
||||||
|
if a[0] != '0' and a[1] != 'x':
|
||||||
|
return false
|
||||||
|
for i in 2..41:
|
||||||
|
let ch = a[i]
|
||||||
|
if ch in {'0'..'9'} or ch in {'a'..'f'}:
|
||||||
|
address &= ch
|
||||||
|
elif ch in {'A'..'F'}:
|
||||||
|
address &= chr(ord(ch) - ord('A') + ord('a'))
|
||||||
|
else:
|
||||||
|
return false
|
||||||
|
var hash = keccak256.digest(address)
|
||||||
|
var hexhash = toHex(hash.data, true)
|
||||||
|
for i in 0..<len(address):
|
||||||
|
if hexhash[i] >= '0' and hexhash[i] <= '7':
|
||||||
|
check.add(address[i])
|
||||||
|
else:
|
||||||
|
if address[i] >= '0' and address[i] <= '9':
|
||||||
|
check.add(address[i])
|
||||||
|
else:
|
||||||
|
let ch = chr(ord(address[i]) - ord('a') + ord('A'))
|
||||||
|
check.add(ch)
|
||||||
|
result = (check == a)
|
||||||
|
|
||||||
|
proc toCanonicalAddress*(pubkey: PublicKey): array[20, byte] =
|
||||||
|
## Convert public key to canonical address.
|
||||||
|
var hash = keccak256.digest(pubkey.getRaw())
|
||||||
|
copyMem(addr result[0], addr hash.data[12], 20)
|
||||||
|
|
||||||
|
proc `$`*(pubkey: PublicKey): string =
|
||||||
|
## Convert public key to hexadecimal string representation.
|
||||||
|
result = toHex(pubkey.getRaw(), true)
|
||||||
|
|
||||||
|
proc `$`*(sig: Signature): string =
|
||||||
|
## Convert signature to hexadecimal string representation.
|
||||||
|
result = toHex(sig.getRaw(), true)
|
||||||
|
|
||||||
|
proc `$`*(seckey: PrivateKey): string =
|
||||||
|
## Convert private key to hexadecimal string representation
|
||||||
|
result = toHex(seckey.data, true)
|
|
@ -0,0 +1,321 @@
|
||||||
|
#
|
||||||
|
# Nim Ethereum Keys (nim-eth-keys)
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# - Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
|
# - MIT license (LICENSE-MIT)
|
||||||
|
#
|
||||||
|
|
||||||
|
## This is libsecp256k1 backend.
|
||||||
|
|
||||||
|
import secp256k1, nimcrypto/sysrand, nimcrypto/utils
|
||||||
|
|
||||||
|
const
|
||||||
|
KeyLength* = 256 div 8
|
||||||
|
RawSignatureSize* = KeyLength * 2 + 1
|
||||||
|
RawPublicKeySize* = KeyLength * 2
|
||||||
|
InvalidPrivateKey = "Invalid private key!"
|
||||||
|
InvalidPublicKey = "Invalid public key!"
|
||||||
|
InvalidSignature = "Invalid signature!"
|
||||||
|
VerificationFailed = "Signature verification has been failed!"
|
||||||
|
MessageSizeError = "Size of message to sign must be KeyLength bytes!"
|
||||||
|
|
||||||
|
type
|
||||||
|
PublicKey* = secp256k1_pubkey
|
||||||
|
## Representation of public key
|
||||||
|
|
||||||
|
PrivateKey* = object
|
||||||
|
## Representation of secret key
|
||||||
|
data*: array[KeyLength, byte]
|
||||||
|
|
||||||
|
SharedSecret* = object
|
||||||
|
## Representation of ECDH shared secret
|
||||||
|
data*: array[KeyLength, byte]
|
||||||
|
|
||||||
|
KeyPair* = object
|
||||||
|
## Representation of private/public keys pair
|
||||||
|
seckey*: PrivateKey
|
||||||
|
pubkey*: PublicKey
|
||||||
|
|
||||||
|
Signature* = secp256k1_ecdsa_recoverable_signature
|
||||||
|
## Representation of signature
|
||||||
|
|
||||||
|
Secp256k1Exception* = object of Exception
|
||||||
|
## Exceptions generated by `libsecp256k1`
|
||||||
|
|
||||||
|
EthKeysContext = ref object
|
||||||
|
context: ptr secp256k1_context
|
||||||
|
error: string
|
||||||
|
|
||||||
|
var ekContext {.threadvar.}: EthKeysContext
|
||||||
|
## Thread local variable which holds current context
|
||||||
|
|
||||||
|
##
|
||||||
|
## Private procedures interface
|
||||||
|
##
|
||||||
|
|
||||||
|
proc illegalCallback(message: cstring; data: pointer) {.cdecl.} =
|
||||||
|
let ctx = cast[EthKeysContext](data)
|
||||||
|
ctx.error = $message
|
||||||
|
|
||||||
|
proc errorCallback(message: cstring, data: pointer) {.cdecl.} =
|
||||||
|
let ctx = cast[EthKeysContext](data)
|
||||||
|
ctx.error = $message
|
||||||
|
|
||||||
|
proc shutdownLibsecp256k1(ekContext: EthKeysContext) =
|
||||||
|
# TODO: use destructor when finalizer are deprecated for destructors
|
||||||
|
if not isNil(ekContext.context):
|
||||||
|
secp256k1_context_destroy(ekContext.context)
|
||||||
|
|
||||||
|
proc newEthKeysContext(): EthKeysContext =
|
||||||
|
## Create new `EthKeysContext`.
|
||||||
|
new(result, shutdownLibsecp256k1)
|
||||||
|
let flags = cuint(SECP256K1_CONTEXT_VERIFY or SECP256K1_CONTEXT_SIGN)
|
||||||
|
result.context = secp256k1_context_create(flags)
|
||||||
|
secp256k1_context_set_illegal_callback(result.context, illegalCallback,
|
||||||
|
cast[pointer](result))
|
||||||
|
secp256k1_context_set_error_callback(result.context, errorCallback,
|
||||||
|
cast[pointer](result))
|
||||||
|
result.error = ""
|
||||||
|
|
||||||
|
proc getSecpContext(): ptr secp256k1_context =
|
||||||
|
## Get current `secp256k1_context`
|
||||||
|
if isNil(ekContext):
|
||||||
|
ekContext = newEthKeysContext()
|
||||||
|
result = ekContext.context
|
||||||
|
|
||||||
|
proc getContext(): EthKeysContext =
|
||||||
|
## Get current `EccContext`
|
||||||
|
if isNil(ekContext):
|
||||||
|
ekContext = newEthKeysContext()
|
||||||
|
result = ekContext
|
||||||
|
|
||||||
|
template raiseSecp256k1Error() =
|
||||||
|
## Raises `libsecp256k1` error as exception
|
||||||
|
let mctx = getContext()
|
||||||
|
if len(mctx.error) > 0:
|
||||||
|
var msg = mctx.error
|
||||||
|
mctx.error.setLen(0)
|
||||||
|
raise newException(Secp256k1Exception, msg)
|
||||||
|
|
||||||
|
proc libsecp256k1ErrorMsg(): string =
|
||||||
|
let mctx = getContext()
|
||||||
|
result = mctx.error
|
||||||
|
|
||||||
|
proc setErrorMsg(m: string) =
|
||||||
|
let mctx = getContext()
|
||||||
|
mctx.error = m
|
||||||
|
|
||||||
|
##
|
||||||
|
## Public procedures interface
|
||||||
|
##
|
||||||
|
|
||||||
|
proc newPrivateKey*(): PrivateKey =
|
||||||
|
## Generates new private key.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
while true:
|
||||||
|
if randomBytes(result.data) == KeyLength:
|
||||||
|
if secp256k1_ec_seckey_verify(ctx, cast[ptr cuchar](addr result)) == 1:
|
||||||
|
break
|
||||||
|
|
||||||
|
proc getPublicKey*(seckey: PrivateKey): PublicKey =
|
||||||
|
## Return public key for private key `seckey`.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
if secp256k1_ec_pubkey_create(ctx, addr result,
|
||||||
|
cast[ptr cuchar](unsafeAddr seckey)) != 1:
|
||||||
|
raiseSecp256k1Error()
|
||||||
|
|
||||||
|
proc newKeyPair*(): KeyPair =
|
||||||
|
## Generates new private and public key.
|
||||||
|
result.seckey = newPrivateKey()
|
||||||
|
result.pubkey = result.seckey.getPublicKey()
|
||||||
|
|
||||||
|
proc initPrivateKey*(hexstr: string): PrivateKey =
|
||||||
|
## Create new private key from hexadecimal string representation.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
var o = fromHex(stripSpaces(hexstr))
|
||||||
|
if len(o) < KeyLength:
|
||||||
|
raise newException(EthKeysException, InvalidPrivateKey)
|
||||||
|
copyMem(addr result, addr o[0], KeyLength)
|
||||||
|
if secp256k1_ec_seckey_verify(ctx, cast[ptr cuchar](addr result)) != 1:
|
||||||
|
raise newException(EthKeysException, InvalidPrivateKey)
|
||||||
|
|
||||||
|
proc initPrivateKey*(data: openarray[byte]): PrivateKey =
|
||||||
|
## Create new private key from binary data blob.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
if len(data) < KeyLength:
|
||||||
|
raise newException(EthKeysException, InvalidPrivateKey)
|
||||||
|
copyMem(addr result, unsafeAddr data[0], KeyLength)
|
||||||
|
if secp256k1_ec_seckey_verify(ctx, cast[ptr cuchar](addr result)) != 1:
|
||||||
|
raise newException(EthKeysException, InvalidPrivateKey)
|
||||||
|
|
||||||
|
proc recoverPublicKey*(data: openarray[byte],
|
||||||
|
pubkey: var PublicKey): EthKeysStatus =
|
||||||
|
## Unserialize public key from `data`.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
let length = len(data)
|
||||||
|
if length < RawPublicKeySize:
|
||||||
|
setErrorMsg(InvalidPublicKey)
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
var rawkey: array[RawPublicKeySize + 1, byte]
|
||||||
|
rawkey[0] = 0x04'u8 # mark key with UNCOMPRESSED flag
|
||||||
|
copyMem(addr rawkey[1], unsafeAddr data[0], RawPublicKeySize)
|
||||||
|
if secp256k1_ec_pubkey_parse(ctx, addr pubkey,
|
||||||
|
cast[ptr cuchar](addr rawkey),
|
||||||
|
RawPublicKeySize + 1) != 1:
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
result = EthKeysStatus.Success
|
||||||
|
|
||||||
|
proc recoverSignature*(data: openarray[byte],
|
||||||
|
signature: var Signature): EthKeysStatus =
|
||||||
|
## Unserialize signature from `data`.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
let length = len(data)
|
||||||
|
if length < RawSignatureSize:
|
||||||
|
setErrorMsg(InvalidSignature)
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
var recid = cint(data[KeyLength * 2])
|
||||||
|
if secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, addr signature,
|
||||||
|
cast[ptr cuchar](unsafeAddr data[0]),
|
||||||
|
recid) != 1:
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
result = EthKeysStatus.Success
|
||||||
|
|
||||||
|
proc initPublicKey*(hexstr: string): PublicKey =
|
||||||
|
## Create new public key from hexadecimal string representation.
|
||||||
|
var o = fromHex(stripSpaces(hexstr))
|
||||||
|
if len(o) < RawPublicKeySize:
|
||||||
|
raise newException(EthKeysException, InvalidPublicKey)
|
||||||
|
if recoverPublicKey(o, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, InvalidPublicKey)
|
||||||
|
|
||||||
|
proc initPublicKey*(data: openarray[byte]): PublicKey =
|
||||||
|
## Create new public key from binary data blob.
|
||||||
|
if recoverPublicKey(data, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, InvalidPublicKey)
|
||||||
|
|
||||||
|
proc initSignature*(hexstr: string): Signature =
|
||||||
|
## Create new signature from hexadecimal string representation.
|
||||||
|
var o = fromHex(stripSpaces(hexstr))
|
||||||
|
if recoverSignature(o, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, libsecp256k1ErrorMsg())
|
||||||
|
|
||||||
|
proc initSignature*(data: openarray[byte]): Signature =
|
||||||
|
## Create new signature from 'data'.
|
||||||
|
if recoverSignature(data, result) != EthKeysStatus.Success:
|
||||||
|
raise newException(EthKeysException, libsecp256k1ErrorMsg())
|
||||||
|
|
||||||
|
proc ecdhAgree*(seckey: PrivateKey, pubkey: PublicKey,
|
||||||
|
secret: var SharedSecret): EthKeysStatus =
|
||||||
|
## Calculate ECDH shared secret.
|
||||||
|
var res: array[KeyLength + 1, byte]
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
if secp256k1_ecdh_raw(ctx, cast[ptr cuchar](addr res),
|
||||||
|
unsafeAddr pubkey,
|
||||||
|
cast[ptr cuchar](unsafeAddr seckey)) != 1:
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
copyMem(addr secret, addr res[1], KeyLength)
|
||||||
|
return(EthKeysStatus.Success)
|
||||||
|
|
||||||
|
proc getRaw*(pubkey: PublicKey): array[RawPublicKeySize, byte] {.noinit.} =
|
||||||
|
## Converts public key `pubkey` to serialized form.
|
||||||
|
var key: array[RawPublicKeySize + 1, byte]
|
||||||
|
var length = csize(sizeof(key))
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
if secp256k1_ec_pubkey_serialize(ctx, cast[ptr cuchar](addr key),
|
||||||
|
addr length, unsafeAddr pubkey,
|
||||||
|
SECP256K1_EC_UNCOMPRESSED) != 1:
|
||||||
|
raiseSecp256k1Error()
|
||||||
|
assert(length == RawPublicKeySize + 1)
|
||||||
|
assert(key[0] == 0x04'u8)
|
||||||
|
copyMem(addr result[0], addr key[1], RawPublicKeySize)
|
||||||
|
|
||||||
|
proc toRaw*(pubkey: PublicKey, data: var openarray[byte]) =
|
||||||
|
## Converts public key `pubkey` to serialized form and store it in `data`.
|
||||||
|
var key: array[RawPublicKeySize + 1, byte]
|
||||||
|
assert(len(data) >= RawPublicKeySize)
|
||||||
|
var length = csize(sizeof(key))
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
if secp256k1_ec_pubkey_serialize(ctx, cast[ptr cuchar](addr key),
|
||||||
|
addr length, unsafeAddr pubkey,
|
||||||
|
SECP256K1_EC_UNCOMPRESSED) != 1:
|
||||||
|
raiseSecp256k1Error()
|
||||||
|
assert(length == RawPublicKeySize + 1)
|
||||||
|
assert(key[0] == 0x04'u8)
|
||||||
|
copyMem(addr data[0], addr key[1], RawPublicKeySize)
|
||||||
|
|
||||||
|
proc getRaw*(s: Signature): array[RawSignatureSize, byte] {.noinit.} =
|
||||||
|
## Converts signature `s` to serialized form.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
var recid = cint(0)
|
||||||
|
if secp256k1_ecdsa_recoverable_signature_serialize_compact(
|
||||||
|
ctx, cast[ptr cuchar](unsafeAddr result), addr recid, unsafeAddr s) != 1:
|
||||||
|
raiseSecp256k1Error()
|
||||||
|
result[64] = uint8(recid)
|
||||||
|
|
||||||
|
proc toRaw*(s: Signature, data: var openarray[byte]) =
|
||||||
|
## Converts signature `s` to serialized form and store it in `data`.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
var recid = cint(0)
|
||||||
|
assert(len(data) >= RawSignatureSize)
|
||||||
|
if secp256k1_ecdsa_recoverable_signature_serialize_compact(
|
||||||
|
ctx, cast[ptr cuchar](addr data[0]), addr recid, unsafeAddr s) != 1:
|
||||||
|
raiseSecp256k1Error()
|
||||||
|
data[64] = uint8(recid)
|
||||||
|
|
||||||
|
proc recoverSignatureKey*(data: openarray[byte],
|
||||||
|
msg: openarray[byte],
|
||||||
|
pubkey: var PublicKey): EthKeysStatus =
|
||||||
|
## Perform check on digitally signed `data` using original message `msg` and
|
||||||
|
## recover public key to `pubkey` on success.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
let length = len(data)
|
||||||
|
if len(msg) < KeyLength:
|
||||||
|
setErrorMsg(MessageSizeError)
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
if length < RawSignatureSize:
|
||||||
|
setErrorMsg(InvalidSignature)
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
var recid = cint(data[KeyLength * 2])
|
||||||
|
var s: secp256k1_ecdsa_recoverable_signature
|
||||||
|
if secp256k1_ecdsa_recoverable_signature_parse_compact(ctx, addr s,
|
||||||
|
cast[ptr cuchar](unsafeAddr data[0]),
|
||||||
|
recid) != 1:
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
if secp256k1_ecdsa_recover(ctx, addr pubkey, addr s,
|
||||||
|
cast[ptr cuchar](msg)) != 1:
|
||||||
|
setErrorMsg(VerificationFailed)
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
result = EthKeysStatus.Success
|
||||||
|
|
||||||
|
proc recoverSignatureKey*(signature: Signature,
|
||||||
|
msg: openarray[byte],
|
||||||
|
pubkey: var PublicKey): EthKeysStatus =
|
||||||
|
## Perform check of `signature` using original message `msg` and
|
||||||
|
## recover public key to `pubkey` on success.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
if len(msg) < KeyLength:
|
||||||
|
setErrorMsg(MessageSizeError)
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
if secp256k1_ecdsa_recover(ctx, addr pubkey, unsafeAddr signature,
|
||||||
|
cast[ptr cuchar](msg)) != 1:
|
||||||
|
setErrorMsg(VerificationFailed)
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
result = EthKeysStatus.Success
|
||||||
|
|
||||||
|
proc signRawMessage*(data: openarray[byte], seckey: PrivateKey,
|
||||||
|
signature: var Signature): EthKeysStatus =
|
||||||
|
## Sign message `data` of `KeyLength` size using private key `seckey` and
|
||||||
|
## store result into `signature`.
|
||||||
|
let ctx = getSecpContext()
|
||||||
|
let length = len(data)
|
||||||
|
if length != KeyLength:
|
||||||
|
setErrorMsg(MessageSizeError)
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
if secp256k1_ecdsa_sign_recoverable(ctx, addr signature,
|
||||||
|
cast[ptr cuchar](unsafeAddr data[0]),
|
||||||
|
cast[ptr cuchar](unsafeAddr seckey),
|
||||||
|
nil, nil) != 1:
|
||||||
|
return(EthKeysStatus.Error)
|
||||||
|
return(EthKeysStatus.Success)
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Nim Eth-keys
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
#
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
#
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
# This is a sample of signatures generated with a known-good implementation of the ECDSA
|
||||||
|
# algorithm, which we use to test our ECC backends. If necessary, it can be generated from scratch
|
||||||
|
# with the following code:
|
||||||
|
#
|
||||||
|
# """python
|
||||||
|
# from devp2p import crypto
|
||||||
|
# from eth_utils import encode_hex
|
||||||
|
# msg = b'message'
|
||||||
|
# msghash = crypto.sha3(b'message')
|
||||||
|
# for secret in ['alice', 'bob', 'eve']:
|
||||||
|
# print("'{}': dict(".format(secret))
|
||||||
|
# privkey = crypto.mk_privkey(secret)
|
||||||
|
# pubkey = crypto.privtopub(privkey)
|
||||||
|
# print(" privkey='{}',".format(encode_hex(privkey)))
|
||||||
|
# print(" pubkey='{}',".format(encode_hex(crypto.privtopub(privkey))))
|
||||||
|
# ecc = crypto.ECCx(raw_privkey=privkey)
|
||||||
|
# sig = ecc.sign(msghash)
|
||||||
|
# print(" sig='{}',".format(encode_hex(sig)))
|
||||||
|
# print(" raw_sig='{}')".format(crypto._decode_sig(sig)))
|
||||||
|
# assert crypto.ecdsa_recover(msghash, sig) == pubkey
|
||||||
|
# """
|
||||||
|
|
||||||
|
import nimcrypto
|
||||||
|
|
||||||
|
type
|
||||||
|
testKeySig* = object
|
||||||
|
privkey*: string
|
||||||
|
pubkey*: string
|
||||||
|
raw_sig*: tuple[v: int, r, s: string]
|
||||||
|
serialized_sig*: string
|
||||||
|
|
||||||
|
let
|
||||||
|
MSG* = "message"
|
||||||
|
MSGHASH* = keccak256.digest(MSG)
|
||||||
|
|
||||||
|
# Conversion done through https://www.mobilefish.com/services/big_number/big_number.php
|
||||||
|
|
||||||
|
let
|
||||||
|
alice* = testKeySig(
|
||||||
|
privkey: "9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501",
|
||||||
|
pubkey: "5eed5fa3a67696c334762bb4823e585e2ee579aba3558d9955296d6c04541b426078dbd48d74af1fd0c72aa1a05147cf17be6b60bdbed6ba19b08ec28445b0ca",
|
||||||
|
raw_sig: (
|
||||||
|
v: 1,
|
||||||
|
r: "B20E2EA5D3CBAA83C1E0372F110CF12535648613B479B64C1A8C1A20C5021F38", # Decimal "80536744857756143861726945576089915884233437828013729338039544043241440681784",
|
||||||
|
s: "0434D07EC5795E3F789794351658E80B7FAF47A46328F41E019D7B853745CDFD" # Decimal "1902566422691403459035240420865094128779958320521066670269403689808757640701"
|
||||||
|
),
|
||||||
|
serialized_sig: "b20e2ea5d3cbaa83c1e0372f110cf12535648613b479b64c1a8c1a20c5021f380434d07ec5795e3f789794351658e80b7faf47a46328f41e019d7b853745cdfd01"
|
||||||
|
)
|
||||||
|
|
||||||
|
bob* = testKeySig(
|
||||||
|
privkey: "38e47a7b719dce63662aeaf43440326f551b8a7ee198cee35cb5d517f2d296a2",
|
||||||
|
pubkey: "347746ccb908e583927285fa4bd202f08e2f82f09c920233d89c47c79e48f937d049130e3d1c14cf7b21afefc057f71da73dec8e8ff74ff47dc6a574ccd5d570",
|
||||||
|
raw_sig: (
|
||||||
|
v: 1,
|
||||||
|
r: "5C48EA4F0F2257FA23BD25E6FCB0B75BBE2FF9BBDA0167118DAB2BB6E31BA76E", # Decimal "41741612198399299636429810387160790514780876799439767175315078161978521003886",
|
||||||
|
s: "691DBDAF2A231FC9958CD8EDD99507121F8184042E075CF10F98BA88ABFF1F36" # Decimal "47545396818609319588074484786899049290652725314938191835667190243225814114102"
|
||||||
|
),
|
||||||
|
serialized_sig: "5c48ea4f0f2257fa23bd25e6fcb0b75bbe2ff9bbda0167118dab2bb6e31ba76e691dbdaf2a231fc9958cd8edd99507121f8184042e075cf10f98ba88abff1f3601"
|
||||||
|
)
|
||||||
|
|
||||||
|
eve* = testKeySig(
|
||||||
|
privkey: "876be0999ed9b7fc26f1b270903ef7b0c35291f89407903270fea611c85f515c",
|
||||||
|
pubkey: "c06641f0d04f64dba13eac9e52999f2d10a1ff0ca68975716b6583dee0318d91e7c2aed363ed22edeba2215b03f6237184833fd7d4ad65f75c2c1d5ea0abecc0",
|
||||||
|
raw_sig: (
|
||||||
|
v: 0,
|
||||||
|
r: "BABEEFC5082D3CA2E0BC80532AB38F9CFB196FB9977401B2F6A98061F15ED603", # Decimal "84467545608142925331782333363288012579669270632210954476013542647119929595395",
|
||||||
|
s: "603D0AF084BF906B2CDF6CDDE8B2E1C3E51A41AF5E9ADEC7F3643B3F1AA2AADF" # Decimal "43529886636775750164425297556346136250671451061152161143648812009114516499167"
|
||||||
|
),
|
||||||
|
serialized_sig: "babeefc5082d3ca2e0bc80532ab38f9cfb196fb9977401b2f6a98061f15ed603603d0af084bf906b2cdf6cdde8b2e1c3e51a41af5e9adec7f3643b3f1aa2aadf00"
|
||||||
|
)
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Nim Eth-keys
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
#
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
#
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
import eth/keys, #../src/private/conversion_bytes,
|
||||||
|
./config
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
suite "Test key and signature data structure":
|
||||||
|
|
||||||
|
test "Signing from private key object (ported from official eth-keys)":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let
|
||||||
|
pk = initPrivateKey(person.privkey)
|
||||||
|
signature = pk.sign_msg(MSG)
|
||||||
|
|
||||||
|
check: verify_msg(pk.public_key, MSG, signature)
|
||||||
|
|
||||||
|
test "Hash signing from private key object (ported from official eth-keys)":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let
|
||||||
|
pk = initPrivateKey(person.privkey)
|
||||||
|
signature = pk.sign_msg(MSGHASH)
|
||||||
|
|
||||||
|
check: verify_msg(pk.public_key, MSGHASH, signature)
|
||||||
|
|
||||||
|
test "Recover public key from message":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let
|
||||||
|
pk = initPrivateKey(person.privkey)
|
||||||
|
signature = pk.sign_msg(MSG)
|
||||||
|
|
||||||
|
recovered_pubkey = recover_pubkey_from_msg(MSG, signature)
|
||||||
|
|
||||||
|
check: pk.public_key == recovered_pubkey
|
||||||
|
|
||||||
|
test "Recover public key from message hash":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let
|
||||||
|
pk = initPrivateKey(person.privkey)
|
||||||
|
signature = pk.sign_msg(MSGHASH)
|
||||||
|
|
||||||
|
recovered_pubkey = recover_pubkey_from_msg(MSGHASH, signature)
|
||||||
|
|
||||||
|
check: pk.public_key == recovered_pubkey
|
||||||
|
|
||||||
|
test "Signature serialization and deserialization":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let
|
||||||
|
pk = initPrivateKey(person.privkey)
|
||||||
|
signature = pk.sign_msg(MSG)
|
||||||
|
deserializedSignature = parseSignature(hexToSeqByteBE(person.serialized_sig))
|
||||||
|
|
||||||
|
var serialized_sig: array[65, byte]
|
||||||
|
signature.serialize(serialized_sig)
|
||||||
|
|
||||||
|
check:
|
||||||
|
signature == deserializedSignature
|
||||||
|
serialized_sig.toHex() == person.serialized_sig
|
||||||
|
$signature == person.serialized_sig
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Nim Eth-keys
|
||||||
|
# Copyright (c) 2018 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
#
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||||
|
#
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
import ../src/eth_keys,
|
||||||
|
./config
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
suite "Testing private -> public key conversion":
|
||||||
|
test "Known private to known public keys (test data from Ethereum eth-keys)":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let privkey = initPrivateKey(person.privkey)
|
||||||
|
|
||||||
|
let computed_pubkey = $privkey.public_key
|
||||||
|
|
||||||
|
check: computed_pubkey == person.pubkey
|
|
@ -0,0 +1,267 @@
|
||||||
|
#
|
||||||
|
# Ethereum P2P
|
||||||
|
# (c) Copyright 2018
|
||||||
|
# Status Research & Development GmbH
|
||||||
|
#
|
||||||
|
# See the file "LICENSE", included in this
|
||||||
|
# distribution, for details about the copyright.
|
||||||
|
#
|
||||||
|
|
||||||
|
import unittest, strutils
|
||||||
|
import eth/keys
|
||||||
|
import nimcrypto/hash, nimcrypto/keccak, nimcrypto/utils
|
||||||
|
|
||||||
|
proc compare(x: openarray[byte], y: openarray[byte]): bool =
|
||||||
|
result = len(x) == len(y)
|
||||||
|
if result:
|
||||||
|
for i in 0..(len(x) - 1):
|
||||||
|
if x[i] != y[i]:
|
||||||
|
result = false
|
||||||
|
break
|
||||||
|
const
|
||||||
|
pkbytes = "58d23b55bc9cdce1f18c2500f40ff4ab7245df9a89505e9b1fa4851f623d241d"
|
||||||
|
message = "message"
|
||||||
|
address = "dc544d1aa88ff8bbd2f2aec754b1f1e99e1812fd"
|
||||||
|
|
||||||
|
alice = [
|
||||||
|
"9c0257114eb9399a2985f8e75dad7600c5d89fe3824ffa99ec1c3eb8bf3b0501",
|
||||||
|
"""5eed5fa3a67696c334762bb4823e585e2ee579aba3558d9955296d6c04541b42
|
||||||
|
6078dbd48d74af1fd0c72aa1a05147cf17be6b60bdbed6ba19b08ec28445b0ca""",
|
||||||
|
"""b20e2ea5d3cbaa83c1e0372f110cf12535648613b479b64c1a8c1a20c5021f38
|
||||||
|
0434d07ec5795e3f789794351658e80b7faf47a46328f41e019d7b853745cdfd01"""
|
||||||
|
]
|
||||||
|
bob = [
|
||||||
|
"38e47a7b719dce63662aeaf43440326f551b8a7ee198cee35cb5d517f2d296a2",
|
||||||
|
"""347746ccb908e583927285fa4bd202f08e2f82f09c920233d89c47c79e48f937
|
||||||
|
d049130e3d1c14cf7b21afefc057f71da73dec8e8ff74ff47dc6a574ccd5d570""",
|
||||||
|
"""5c48ea4f0f2257fa23bd25e6fcb0b75bbe2ff9bbda0167118dab2bb6e31ba76e
|
||||||
|
691dbdaf2a231fc9958cd8edd99507121f8184042e075cf10f98ba88abff1f3601"""
|
||||||
|
]
|
||||||
|
eve = [
|
||||||
|
"876be0999ed9b7fc26f1b270903ef7b0c35291f89407903270fea611c85f515c",
|
||||||
|
"""c06641f0d04f64dba13eac9e52999f2d10a1ff0ca68975716b6583dee0318d91
|
||||||
|
e7c2aed363ed22edeba2215b03f6237184833fd7d4ad65f75c2c1d5ea0abecc0""",
|
||||||
|
"""babeefc5082d3ca2e0bc80532ab38f9cfb196fb9977401b2f6a98061f15ed603
|
||||||
|
603d0af084bf906b2cdf6cdde8b2e1c3e51a41af5e9adec7f3643b3f1aa2aadf00"""
|
||||||
|
]
|
||||||
|
|
||||||
|
suite "ECC/ECDSA/ECDHE tests suite":
|
||||||
|
test "Known private to known public keys (test data from Ethereum eth-keys)":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let privkey = initPrivateKey(person[0])
|
||||||
|
var pubkeyHex = $privkey.getPublicKey()
|
||||||
|
check:
|
||||||
|
pubkeyHex == stripSpaces(person[1])
|
||||||
|
|
||||||
|
test "Recover public key from message":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let privkey = initPrivateKey(person[0])
|
||||||
|
let signature = privkey.signMessage(message)
|
||||||
|
let recoveredKey = signature.recoverKeyFromSignature(message)
|
||||||
|
check:
|
||||||
|
$privkey.getPublicKey() == $recoveredKey
|
||||||
|
|
||||||
|
test "Signature serialization and deserialization":
|
||||||
|
for person in [alice, bob, eve]:
|
||||||
|
let privkey = initPrivateKey(person[0])
|
||||||
|
let signature = privkey.signMessage(message)
|
||||||
|
let expectSignature = initSignature(person[2])
|
||||||
|
check:
|
||||||
|
$signature == $expectSignature
|
||||||
|
|
||||||
|
test "test_signing_from_private_key_obj":
|
||||||
|
var s = initPrivateKey(pkbytes)
|
||||||
|
var signature = s.signMessage(message)
|
||||||
|
var mhash = keccak256.digest(message)
|
||||||
|
check verifyMessage(signature.data, mhash) == true
|
||||||
|
|
||||||
|
test "test_recover_from_signature_obj":
|
||||||
|
var s = initPrivateKey(pkbytes)
|
||||||
|
var mhash = keccak256.digest(message)
|
||||||
|
var signature = s.signMessage(message)
|
||||||
|
var p = recoverKeyFromSignature(signature, mhash)
|
||||||
|
check:
|
||||||
|
s.getPublicKey() == p
|
||||||
|
|
||||||
|
test "test_to_address_from_public_key":
|
||||||
|
var s = initPrivateKey(pkbytes)
|
||||||
|
var chk = s.getPublicKey().toAddress()
|
||||||
|
var expect = "0x" & address
|
||||||
|
check chk == expect
|
||||||
|
|
||||||
|
test "test_to_canonical_address_from_public_key":
|
||||||
|
var s = initPrivateKey(pkbytes)
|
||||||
|
var chk = s.getPublicKey().toCanonicalAddress()
|
||||||
|
var expect = fromHex(stripSpaces(address))
|
||||||
|
check compare(chk, expect) == true
|
||||||
|
|
||||||
|
test "test_to_checksum_address_from_public_key":
|
||||||
|
var s = initPrivateKey(pkbytes)
|
||||||
|
var chk = s.getPublicKey().toChecksumAddress()
|
||||||
|
var expect = "0x" & address
|
||||||
|
check:
|
||||||
|
chk.toLowerAscii() == expect
|
||||||
|
|
||||||
|
test "EIP-55 checksum addresses test cases":
|
||||||
|
var checks = [
|
||||||
|
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
|
||||||
|
"0x52908400098527886E0F7030069857D2E4169EE7",
|
||||||
|
"0x8617E340B3D01FA5F11F306F4090FD50E238070D",
|
||||||
|
"0xde709f2102306220921060314715629080e2fb77",
|
||||||
|
"0x27b1fdb04752bbc536007a920d24acb045561c26",
|
||||||
|
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
|
||||||
|
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
|
||||||
|
"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
|
||||||
|
"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"
|
||||||
|
]
|
||||||
|
var badchecks = [
|
||||||
|
"",
|
||||||
|
"0xXB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
|
||||||
|
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d35X",
|
||||||
|
"0XfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
|
||||||
|
"XXfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
|
||||||
|
"0xfB6916095"
|
||||||
|
]
|
||||||
|
for item in checks:
|
||||||
|
check validateChecksumAddress(item) == true
|
||||||
|
for item in badchecks:
|
||||||
|
check validateChecksumAddress(item) == false
|
||||||
|
|
||||||
|
test "EIP-55 100 addresses":
|
||||||
|
for i in 1..100:
|
||||||
|
var kp = newKeyPair()
|
||||||
|
var chaddress = kp.pubkey.toChecksumAddress()
|
||||||
|
var noaddress = kp.pubkey.toAddress()
|
||||||
|
if noaddress != chaddress:
|
||||||
|
check validateChecksumAddress(noaddress) == false
|
||||||
|
check validateChecksumAddress(chaddress) == true
|
||||||
|
|
||||||
|
test "ECDHE/py-evm test_ecies.py#L19":
|
||||||
|
# ECDHE test vectors
|
||||||
|
# Copied from
|
||||||
|
# https://github.com/ethereum/py-evm/blob/master/tests/p2p/test_ecies.py#L19
|
||||||
|
const privateKeys = [
|
||||||
|
"332143e9629eedff7d142d741f896258f5a1bfab54dab2121d3ec5000093d74b",
|
||||||
|
"7ebbc6a8358bc76dd73ebc557056702c8cfc34e5cfcd90eb83af0347575fd2ad"
|
||||||
|
]
|
||||||
|
const publicKeys = [
|
||||||
|
"""f0d2b97981bd0d415a843b5dfe8ab77a30300daab3658c578f2340308a2da1a07
|
||||||
|
f0821367332598b6aa4e180a41e92f4ebbae3518da847f0b1c0bbfe20bcf4e1""",
|
||||||
|
"""83ede0f19c3c98649265956a4193677b14c338a22de2086a08d84e4446fe37e4e
|
||||||
|
233478259ec90dbeef52f4f6c890f8c38660ec7b61b9d439b8a6d1c323dc025"""
|
||||||
|
]
|
||||||
|
const sharedSecrets = [
|
||||||
|
"ee1418607c2fcfb57fda40380e885a707f49000a5dda056d828b7d9bd1f29a08",
|
||||||
|
"167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62"
|
||||||
|
]
|
||||||
|
var secret: SharedSecret
|
||||||
|
for i in 0..1:
|
||||||
|
var s = privateKeys[i].initPrivateKey()
|
||||||
|
var p = publicKeys[i].initPublicKey()
|
||||||
|
let expect = fromHex(stripSpaces(sharedSecrets[i]))
|
||||||
|
check:
|
||||||
|
ecdhAgree(s, p, secret) == EthKeysStatus.Success
|
||||||
|
compare(expect, secret.data) == true
|
||||||
|
|
||||||
|
test "ECDHE/cpp-ethereum crypto.cpp#L394":
|
||||||
|
# ECDHE test vectors
|
||||||
|
# Copied from https://github.com/ethereum/cpp-ethereum/blob/develop/test/unittests/libdevcrypto/crypto.cpp#L394
|
||||||
|
var expectm = """
|
||||||
|
8ac7e464348b85d9fdfc0a81f2fdc0bbbb8ee5fb3840de6ed60ad9372e718977"""
|
||||||
|
var secret: SharedSecret
|
||||||
|
var s = initPrivateKey(keccak256.digest("ecdhAgree").data)
|
||||||
|
var p = s.getPublicKey()
|
||||||
|
let expect = fromHex(stripSpaces(expectm))
|
||||||
|
check:
|
||||||
|
ecdhAgree(s, p, secret) == EthKeysStatus.Success
|
||||||
|
compare(expect, secret.data) == true
|
||||||
|
|
||||||
|
test "ECDHE/cpp-ethereum rlpx.cpp#L425":
|
||||||
|
# ECDHE test vectors
|
||||||
|
# Copied from https://github.com/ethereum/cpp-ethereum/blob/2409d7ec7d34d5ff5770463b87eb87f758e621fe/test/unittests/libp2p/rlpx.cpp#L425
|
||||||
|
var s0 = """
|
||||||
|
332143e9629eedff7d142d741f896258f5a1bfab54dab2121d3ec5000093d74b"""
|
||||||
|
var p0 = """
|
||||||
|
f0d2b97981bd0d415a843b5dfe8ab77a30300daab3658c578f2340308a2da1a0
|
||||||
|
7f0821367332598b6aa4e180a41e92f4ebbae3518da847f0b1c0bbfe20bcf4e1"""
|
||||||
|
var e0 = """
|
||||||
|
ee1418607c2fcfb57fda40380e885a707f49000a5dda056d828b7d9bd1f29a08"""
|
||||||
|
var secret: SharedSecret
|
||||||
|
var s = initPrivateKey(s0)
|
||||||
|
var p = initPublicKey(p0)
|
||||||
|
let expect = fromHex(stripSpaces(e0))
|
||||||
|
check:
|
||||||
|
ecdhAgree(s, p, secret) == Success
|
||||||
|
compare(expect, secret.data) == true
|
||||||
|
|
||||||
|
test "ECDSA/cpp-ethereum crypto.cpp#L132":
|
||||||
|
# ECDSA test vectors
|
||||||
|
# Copied from https://github.com/ethereum/cpp-ethereum/blob/develop/test/unittests/libdevcrypto/crypto.cpp#L132
|
||||||
|
var signature = """
|
||||||
|
b826808a8c41e00b7c5d71f211f005a84a7b97949d5e765831e1da4e34c9b8295d
|
||||||
|
2a622eee50f25af78241c1cb7cfff11bcf2a13fe65dee1e3b86fd79a4e3ed000"""
|
||||||
|
var pubkey = """
|
||||||
|
e40930c838d6cca526795596e368d16083f0672f4ab61788277abfa23c3740e1cc
|
||||||
|
84453b0b24f49086feba0bd978bb4446bae8dff1e79fcc1e9cf482ec2d07c3"""
|
||||||
|
var check1 = fromHex(stripSpaces(signature))
|
||||||
|
var check2 = fromHex(stripSpaces(pubkey))
|
||||||
|
var sig: Signature
|
||||||
|
var key: PublicKey
|
||||||
|
var s = initPrivateKey(keccak256.digest("sec").data)
|
||||||
|
var m = keccak256.digest("msg").data
|
||||||
|
check signRawMessage(m, s, sig) == Success
|
||||||
|
var sersig = sig.getRaw()
|
||||||
|
check recoverSignatureKey(sersig, m, key) == Success
|
||||||
|
var serkey = key.getRaw()
|
||||||
|
check:
|
||||||
|
compare(sersig, check1) == true
|
||||||
|
compare(serkey, check2) == true
|
||||||
|
|
||||||
|
test "ECDSA/100 signatures":
|
||||||
|
# signature test
|
||||||
|
var rkey: PublicKey
|
||||||
|
var sig: Signature
|
||||||
|
for i in 1..100:
|
||||||
|
var m = newPrivateKey().data
|
||||||
|
var s = newPrivateKey()
|
||||||
|
var key = s.getPublicKey()
|
||||||
|
check signRawMessage(m, s, sig) == Success
|
||||||
|
var sersig = sig.getRaw()
|
||||||
|
check:
|
||||||
|
recoverSignatureKey(sersig, m, rkey) == Success
|
||||||
|
key == rkey
|
||||||
|
|
||||||
|
test "KEYS/100 create/recovery keys":
|
||||||
|
# key create/recovery test
|
||||||
|
var rkey: PublicKey
|
||||||
|
for i in 1..100:
|
||||||
|
var s = newPrivateKey()
|
||||||
|
var key = s.getPublicKey()
|
||||||
|
check:
|
||||||
|
recoverPublicKey(key.getRaw(), rkey) == Success
|
||||||
|
key == rkey
|
||||||
|
|
||||||
|
test "ECDHE/100 shared secrets":
|
||||||
|
# ECDHE shared secret test
|
||||||
|
var secret1, secret2: SharedSecret
|
||||||
|
for i in 1..100:
|
||||||
|
var aliceSecret = newPrivateKey()
|
||||||
|
var alicePublic = aliceSecret.getPublicKey()
|
||||||
|
var bobSecret = newPrivateKey()
|
||||||
|
var bobPublic = bobSecret.getPublicKey()
|
||||||
|
check:
|
||||||
|
ecdhAgree(aliceSecret, bobPublic, secret1) == Success
|
||||||
|
ecdhAgree(bobSecret, alicePublic, secret2) == Success
|
||||||
|
secret1 == secret2
|
||||||
|
|
||||||
|
test "isZeroKey() checks":
|
||||||
|
var seckey1: PrivateKey
|
||||||
|
var pubkey1: PublicKey
|
||||||
|
var seckey2 = newPrivateKey()
|
||||||
|
var pubkey2 = seckey2.getPublicKey()
|
||||||
|
|
||||||
|
check:
|
||||||
|
seckey1.isZeroKey() == true
|
||||||
|
pubkey1.isZeroKey() == true
|
||||||
|
seckey2.isZeroKey() == false
|
||||||
|
pubkey2.isZeroKey() == false
|
Loading…
Reference in New Issue