Hide the (r,s) in signature

This commit is contained in:
mratsim 2018-03-21 17:59:50 +01:00
commit 4454750f95
4 changed files with 153 additions and 22 deletions

View File

@ -19,12 +19,22 @@ proc test(name: string, lang: string = "c") =
switch("out", ("./build/" & name)) switch("out", ("./build/" & name))
setCommand lang, "tests/" & name & ".nim" setCommand lang, "tests/" & name & ".nim"
task test, "Run all tests - C & libsecp256k1 backend": task test_c, "Run all tests - C only & libsecp256k1 backend":
test "all_tests" test "all_tests"
task test_cpp, "Run all tests - C++ & libsecp256k1 backend": task test_cpp, "Run all tests - C++ only & libsecp256k1 backend":
test "all_tests", "cpp" test "all_tests", "cpp"
task test, "Run all tests - C and C++ & libsecp256k1 backend":
## TODO: This only runs the C++ tests ...
# block:
# test "all_tests"
# block:
# test "all_tests", "cpp"
exec "nimble test_c"
exec "rm ./nimcache/*"
exec "nimble test_cpp"
task test_backend_native, "Run all tests - pure Nim backend": task test_backend_native, "Run all tests - pure Nim backend":
switch("define", "backend_native") switch("define", "backend_native")
test "all_tests", "cpp" test "all_tests", "cpp"

105
src/datatypes.md Normal file
View File

@ -0,0 +1,105 @@
# Secp256k1 Implementation details (MIT License)
Details on the secp256k1 implementation.
The Nim datatype is:
```Nim
type
Scalar256 = distinct array[32, byte]
# Secp256k1 makes the signature an opaque "implementation dependant". See details in datatypes.md
# We hide the information too as the native backend might choose a different r,s representation.
Signature* {.packed.}= object
Fr*: Scalar256
Fs*: Scalar256
Fv*: range[0.byte .. 1.byte] # This should be 27..28 as per Ethereum but it's 0..1 in eth-keys ...
```
## Conversion to and from uint256 <-> byte array
https://github.com/bitcoin-core/secp256k1/blob/0b7024185045a49a1a6a4c5615bf31c94f63d9c4/src/scalar_low_impl.h#L47-L61
```C
static void secp256k1_scalar_set_b32(secp256k1_scalar *r, const unsigned char *b32, int *overflow) {
const int base = 0x100 % EXHAUSTIVE_TEST_ORDER;
int i;
*r = 0;
for (i = 0; i < 32; i++) {
*r = ((*r * base) + b32[i]) % EXHAUSTIVE_TEST_ORDER;
}
/* just deny overflow, it basically always happens */
if (overflow) *overflow = 0;
}
static void secp256k1_scalar_get_b32(unsigned char *bin, const secp256k1_scalar* a) {
memset(bin, 0, 32);
bin[28] = *a >> 24; bin[29] = *a >> 16; bin[30] = *a >> 8; bin[31] = *a;
}
```
## Loading, saving, parsing, serializing the signature
https://github.com/bitcoin-core/secp256k1/blob/0b7024185045a49a1a6a4c5615bf31c94f63d9c4/src/modules/recovery/main_impl.h#L12-L72
```C
static void secp256k1_ecdsa_recoverable_signature_load(const secp256k1_context* ctx, secp256k1_scalar* r, secp256k1_scalar* s, int* recid, const secp256k1_ecdsa_recoverable_signature* sig) {
(void)ctx;
if (sizeof(secp256k1_scalar) == 32) {
/* When the secp256k1_scalar type is exactly 32 byte, use its
* representation inside secp256k1_ecdsa_signature, as conversion is very fast.
* Note that secp256k1_ecdsa_signature_save must use the same representation. */
memcpy(r, &sig->data[0], 32);
memcpy(s, &sig->data[32], 32);
} else {
secp256k1_scalar_set_b32(r, &sig->data[0], NULL);
secp256k1_scalar_set_b32(s, &sig->data[32], NULL);
}
*recid = sig->data[64];
}
static void secp256k1_ecdsa_recoverable_signature_save(secp256k1_ecdsa_recoverable_signature* sig, const secp256k1_scalar* r, const secp256k1_scalar* s, int recid) {
if (sizeof(secp256k1_scalar) == 32) {
memcpy(&sig->data[0], r, 32);
memcpy(&sig->data[32], s, 32);
} else {
secp256k1_scalar_get_b32(&sig->data[0], r);
secp256k1_scalar_get_b32(&sig->data[32], s);
}
sig->data[64] = recid;
}
int secp256k1_ecdsa_recoverable_signature_parse_compact(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature* sig, const unsigned char *input64, int recid) {
secp256k1_scalar r, s;
int ret = 1;
int overflow = 0;
(void)ctx;
ARG_CHECK(sig != NULL);
ARG_CHECK(input64 != NULL);
ARG_CHECK(recid >= 0 && recid <= 3);
secp256k1_scalar_set_b32(&r, &input64[0], &overflow);
ret &= !overflow;
secp256k1_scalar_set_b32(&s, &input64[32], &overflow);
ret &= !overflow;
if (ret) {
secp256k1_ecdsa_recoverable_signature_save(sig, &r, &s, recid);
} else {
memset(sig, 0, sizeof(*sig));
}
return ret;
}
int secp256k1_ecdsa_recoverable_signature_serialize_compact(const secp256k1_context* ctx, unsigned char *output64, int *recid, const secp256k1_ecdsa_recoverable_signature* sig) {
secp256k1_scalar r, s;
(void)ctx;
ARG_CHECK(output64 != NULL);
ARG_CHECK(sig != NULL);
ARG_CHECK(recid != NULL);
secp256k1_ecdsa_recoverable_signature_load(ctx, &r, &s, recid, sig);
secp256k1_scalar_get_b32(&output64[0], &r);
secp256k1_scalar_get_b32(&output64[32], &s);
return 1;
}
```

View File

@ -19,8 +19,17 @@ type
Fraw_key*: array[32, byte] Fraw_key*: array[32, byte]
Fpublic_key*: PublicKey Fpublic_key*: PublicKey
type
Scalar256 = array[32, byte]
# Secp256k1 makes the signature an opaque "implementation dependent".
#
# Scalar256 is opaque/distinct too as in practice, they are uint256
# and by default we don't load any.
# See implementation details in datatypes.md.
Signature* {.packed.}= object Signature* {.packed.}= object
Fr*: array[32, byte] Fr*: Scalar256
Fs*: array[32, byte] Fs*: Scalar256
Fv*: range[0.byte .. 1.byte] # This should be 27..28 as per Ethereum but it's 0..1 in eth-keys ... Fv*: range[0.byte .. 1.byte] # This should be 27..28 as per Ethereum but it's 0..1 in eth-keys ...

View File

@ -13,16 +13,20 @@ import ../src/eth_keys, ../src/private/conversion_bytes,
import unittest import unittest
suite "Test key and signature data structure": suite "Test key and signature data structure":
test "Signing from private key object":
for person in [alice, bob, eve]:
let
pk = initPrivateKey(person.privkey)
signature = pk.sign_msg(MSG)
check: # TODO: For now due to needing hex <-> uint256 conversion
signature.Fv == person.raw_sig.v # Testing r, s, v direct access is disabled
signature.Fr == hexToByteArrayBE[32](person.raw_sig.r) #
signature.Fs == hexToByteArrayBE[32](person.raw_sig.s) # test "Signing from private key object":
# for person in [alice, bob, eve]:
# let
# pk = initPrivateKey(person.privkey)
# signature = pk.sign_msg(MSG)
# check:
# signature.Fv == person.raw_sig.v
# signature.Fr == hexToByteArrayBE[32](person.raw_sig.r)
# signature.Fs == hexToByteArrayBE[32](person.raw_sig.s)
test "Signing from private key object (ported from official eth-keys)": test "Signing from private key object (ported from official eth-keys)":
for person in [alice, bob, eve]: for person in [alice, bob, eve]:
@ -32,16 +36,19 @@ suite "Test key and signature data structure":
check: verify_msg(pk.Fpublic_key, MSG, signature) check: verify_msg(pk.Fpublic_key, MSG, signature)
test "Hash signing from private key object": # TODO: For now due to needing hex <-> uint256 conversion
for person in [alice, bob, eve]: # Testing r, s, v direct access is disabled
let #
pk = initPrivateKey(person.privkey) # test "Hash signing from private key object":
signature = pk.sign_msg(MSG) # for person in [alice, bob, eve]:
# let
# pk = initPrivateKey(person.privkey)
# signature = pk.sign_msg(MSG)
check: # check:
signature.Fv == person.raw_sig.v # signature.Fv == person.raw_sig.v
signature.Fr == hexToByteArrayBE[32](person.raw_sig.r) # signature.Fr == hexToByteArrayBE[32](person.raw_sig.r)
signature.Fs == hexToByteArrayBE[32](person.raw_sig.s) # signature.Fs == hexToByteArrayBE[32](person.raw_sig.s)
test "Hash signing from private key object (ported from official eth-keys)": test "Hash signing from private key object (ported from official eth-keys)":
for person in [alice, bob, eve]: for person in [alice, bob, eve]: