From 8ddd78ed68befd447308f711f9ced7802f8b010f Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Tue, 2 Mar 2021 07:37:38 +0100 Subject: [PATCH] Sign Nitro states --- nitro.nimble | 1 + nitro/signature.nim | 48 +++++++++++++++++++++++++++++++++++ tests/nitro/testSignature.nim | 47 ++++++++++++++++++++++++++++++++++ tests/testAll.nim | 1 + 4 files changed, 97 insertions(+) create mode 100644 nitro/signature.nim create mode 100644 tests/nitro/testSignature.nim diff --git a/nitro.nimble b/nitro.nimble index c0f49e9..983a6dc 100644 --- a/nitro.nimble +++ b/nitro.nimble @@ -5,5 +5,6 @@ description = "Nitro state channels" requires "nim >= 1.2.6 & < 2.0.0" requires "nimcrypto >= 0.5.4 & < 0.6.0" +requires "secp256k1" requires "stint" requires "stew" diff --git a/nitro/signature.nim b/nitro/signature.nim new file mode 100644 index 0000000..1cb01f8 --- /dev/null +++ b/nitro/signature.nim @@ -0,0 +1,48 @@ +import pkg/nimcrypto +import pkg/secp256k1 +import pkg/stew/byteutils +import ./state + +export toPublicKey + +type + PrivateKey* = SkSecretKey + PublicKey* = SkPublicKey + Signature* = SkRecoverableSignature + +proc rng(data: var openArray[byte]): bool = + randomBytes(data) == data.len + +proc random*(_: type PrivateKey): PrivateKey = + PrivateKey.random(rng).get() + +proc `$`*(key: PrivateKey): string = + key.toHex() + +proc parse*(_: type PrivateKey, s: string): PrivateKey = + SkSecretKey.fromHex(s).tryGet() + +proc sign(key: PrivateKey, data: openArray[byte]): Signature = + let hash = keccak256.digest(data).data + key.signRecoverable(SkMessage(hash)) + +proc signMessage(key: PrivateKey, message: openArray[byte]): Signature = + # https://eips.ethereum.org/EIPS/eip-191 + var data: seq[byte] + data.add("\x19Ethereum Signed Message:\n".toBytes) + data.add(($message.len).toBytes) + data.add(message) + key.sign(data) + +proc sign*(key: PrivateKey, state: State): Signature = + key.signMessage(hashState(state)) + +proc `$`*(signature: Signature): string = + var bytes = signature.toRaw() + bytes[64] += 27 + bytes.toHex() + +proc parse*(_: type Signature, s: string): Signature = + var bytes = array[65, byte].fromHex(s) + bytes[64] -= 27 + SkRecoverableSignature.fromRaw(bytes).tryGet() diff --git a/tests/nitro/testSignature.nim b/tests/nitro/testSignature.nim new file mode 100644 index 0000000..0ad4f2d --- /dev/null +++ b/tests/nitro/testSignature.nim @@ -0,0 +1,47 @@ +import std/unittest +import pkg/nimcrypto +import pkg/secp256k1 +import pkg/stew/byteutils +import pkg/nitro/state +import pkg/nitro/signature +import ./examples + +suite "signature": + + test "signs state hashes": + let state = State.example + let privateKey = PrivateKey.random() + let publicKey = privateKey.toPublicKey() + + let signature = privateKey.sign(state) + + let message = hashState(state) + let data = "\x19Ethereum Signed Message:\n32".toBytes & @message + let hash = keccak256.digest(data).data + check recover(signature, SkMessage(hash)).tryGet() == publicKey + + test "produces the same signatures as the javascript implementation": + let state =State( + channel: Channel( + chainId: 0x1.u256, + nonce: 1, + participants: @[ + EthAddress.fromHex("0x8a64E10FF40Bc9C90EA5750313dB5e036495c10E") + ] + ), + outcome: Outcome(@[]), + turnNum: 1, + isFinal: false, + appData: @[0'u8], + appDefinition: EthAddress.default, + challengeDuration: 5 + ) + let seckey = PrivateKey.parse( + "41b0f5f91967dded8af487277874f95116094cc6004ac2b2169b5b6a87608f3e" + ) + let expected = Signature.parse( + "9b966cf0065586d59c8b9eb475ac763c96ad8316b81061238f32968a631f9e21" & + "251363c193c78c89b3eb2fec23f0ea5c3c72acff7d1f27430cfb84b9da9831fb" & + "1c" + ) + check seckey.sign(state) == expected diff --git a/tests/testAll.nim b/tests/testAll.nim index fe926c6..81f65d1 100644 --- a/tests/testAll.nim +++ b/tests/testAll.nim @@ -2,5 +2,6 @@ import ./nitro/testAbi import ./nitro/testChannel import ./nitro/testOutcome import ./nitro/testState +import ./nitro/testSignature {.warning[UnusedImport]: off.}