You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There should be a doAssert / check size of the secpPublicKey string.
Also Public Keys have several serialized representations depending if they start with a:
0x04 (uncompressed - 64 bytes)
0x02 or 0x03 (compressed - 33 bytes)
0x06 or 0x07 (hybrid - 65 bytes)
You should use secp256k1_ec_pubkey_parse instead:
It will handle all those cases
secp256k1 operations are constant time (for division for example) and will protect the wallet against timing attacks.
The secp256k1_pubkey data structure is an array[64, byte] but what is inside is implementation defined. It can change depending on platform (x86, ARM), endianness and libsecp256k1 version.
/** Parse a variable-length public key into the pubkey object. * * Returns: 1 if the public key was fully valid. * 0 if the public key could not be parsed or is invalid. * Args: ctx: a secp256k1 context object. * Out: pubkey: pointer to a pubkey object. If 1 is returned, it is set to a * parsed version of input. If not, its value is undefined. * In: input: pointer to a serialized public key * inputlen: length of the array pointed to by input * * This function supports parsing compressed (33 bytes, header byte 0x02 or * 0x03), uncompressed (65 bytes, header byte 0x04), or hybrid (65 bytes, header * byte 0x06 or 0x07) format public keys. */SECP256K1_APISECP256K1_WARN_UNUSED_RESULTintsecp256k1_ec_pubkey_parse(
constsecp256k1_context*ctx,
secp256k1_pubkey*pubkey,
constunsigned char*input,
size_tinputlen
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
So in memory you work with the libsecp256k1 representation, but you use the serialized version for storage or communication.
procparsePublicKeyWithPrefix(data: openarray[byte], result: varPublicKey) =## Parse a variable-length public key into the PublicKey objectifsecp256k1_ec_pubkey_parse(ctx, result.asPtrPubKey, cast[ptrcuchar](unsafeAddr data[0]), data.len.csize) !=1:
raisenewException(Exception, "Could not parse public key")
procparsePublicKey*(data: openarray[byte]): PublicKey=## Parse a variable-length public key into the PublicKey objectcase data.len
of65:
parsePublicKeyWithPrefix(data, result)
of64:
var tmpData: Serialized_PubKey
copyMem(addr tmpData[1], unsafeAddr data[0], 64)
tmpData[0] =0x04parsePublicKeyWithPrefix(tmpData, result)
else: #TODO: Support other lengthsraisenewException(Exception, "Wrong public key length")
Performance:
From a performance point of view you should change (uint8) parseHexInt(pubKey[i .. i + 1]) to (uint8) parseHexInt(pubKey.toOpenarray(i, i+1)) because slicing will create a new seq allocated on the heap while toOpenarray provides a view. Note that toopenarray requires devel. In any case this doesn't really matter because the string should be passed to libsecp256k1 anyway.
procparseSignature*(data: openarray[byte], fromIdx: int=0): Signature=## Parse a compact ECDSA signature. Bytes [fromIdx .. fromIdx + 63] of `data`## should contain the signature, byte [fromIdx + 64] should contain the recovery id.assert(data.len - fromIdx >=65)
ifsecp256k1_ecdsa_recoverable_signature_parse_compact(ctx,
result.asPtrRecoverableSignature,
cast[ptrcuchar](unsafeAddr data[fromIdx]),
cint(data[fromIdx +64])) !=1:
raisenewException(ValueError, "Signature data is invalid")
Note: when trying to do a pure Nim secp256k1 compatible lib, in my tests I couldn't understand the in-memory ECDSA signature representation so just use libsecp256k1 proc.
Serialization of Public key and Signature
A serialized public key or signature is what we use "openly".
You should use the corresponding secp256k1_ec_pubkey_serialize and secp256k1_ecdsa_recoverable_signature_serialize_compact (for a recoverable 65 bytes signature) or secp256k1_ec_pubkey_serialize (for a 64 bytes signature). We use the recoverable signature for Ethereum.
You should not convert to cstring for perf, you should do cast[ptr cuchar](hash[0].addr) or cast[ptr cuchar](hash[0].unsafeAddr) instead to avoid extra allocation, the hash does not need to be a var parameter
For verification, alternatively with recoverable signatures you can use secp256k1_ecdsa_recoverable_signature_parse_compact to retrieve the signature the message was signed with like here.
Hey @kayabaNerve,
Here is my review of SECP256K1Wrapper.nim as of https://github.com/kayabaNerve/Ember/blob/f3da08adf7ef8d819416a9e87e1a4d07bfecd779/src/lib/SECP256K1Wrapper.nim#L17
General notes:
Make sure to check or Ethereum keys wrapper: https://github.com/status-im/nim-eth-keys/blob/master/eth_keys/libsecp256k1.nim
and also the old API that I wrote: https://github.com/status-im/nim-eth-keys/blob/master/old_api/backend_libsecp256k1/libsecp256k1.nim
(License MIT/Apache v2)
Note that both are untested yet and not audited.
secpPublicKey
https://github.com/kayabaNerve/Ember/blob/f3da08adf7ef8d819416a9e87e1a4d07bfecd779/src/lib/SECP256K1Wrapper.nim#L17-L21
Security
There should be a doAssert / check size of the secpPublicKey string.
Also Public Keys have several serialized representations depending if they start with a:
You should use
secp256k1_ec_pubkey_parse
instead:array[64, byte]
but what is inside is implementation defined. It can change depending on platform (x86, ARM), endianness and libsecp256k1 version.https://github.com/status-im/secp256k1/blob/be6f5385330905bf1d7cc441be6703cfa7aef847/include/secp256k1.h#L281-L300
So in memory you work with the libsecp256k1 representation, but you use the serialized version for storage or communication.
Example - new Eth-key API:
or old API
Performance:
From a performance point of view you should change
(uint8) parseHexInt(pubKey[i .. i + 1])
to(uint8) parseHexInt(pubKey.toOpenarray(i, i+1))
because slicing will create a new seq allocated on the heap while toOpenarray provides a view. Note that toopenarray requires devel. In any case this doesn't really matter because the string should be passed to libsecp256k1 anyway.secpSignature
https://github.com/kayabaNerve/Ember/blob/f3da08adf7ef8d819416a9e87e1a4d07bfecd779/src/lib/SECP256K1Wrapper.nim#L23-L32
Security
Similar to public key you should use
secp256k1_ecdsa_recoverable_signature_parse_compact
like in the new API:or the old one
Note: when trying to do a pure Nim secp256k1 compatible lib, in my tests I couldn't understand the in-memory ECDSA signature representation so just use libsecp256k1 proc.
Serialization of Public key and Signature
A serialized public key or signature is what we use "openly".
You should use the corresponding
secp256k1_ec_pubkey_serialize
andsecp256k1_ecdsa_recoverable_signature_serialize_compact
(for a recoverable 65 bytes signature) orsecp256k1_ec_pubkey_serialize
(for a 64 bytes signature). We use the recoverable signature for Ethereum.New API
Old API Signature and Public Key
Signing and verify
https://github.com/kayabaNerve/Ember/blob/f3da08adf7ef8d819416a9e87e1a4d07bfecd779/src/lib/SECP256K1Wrapper.nim#L34-L55
You should not convert to cstring for perf, you should do
cast[ptr cuchar](hash[0].addr)
orcast[ptr cuchar](hash[0].unsafeAddr)
instead to avoid extra allocation, the hash does not need to be a var parameterFor verification, alternatively with recoverable signatures you can use secp256k1_ecdsa_recoverable_signature_parse_compact to retrieve the signature the message was signed with like here.
Test vectors
Please refer to nim-eth-keys tests and the eth-keys tests from the Ethereum Foundation repo
Tests in the tests.nim file https://github.com/status-im/nim-eth-keys/blob/master/tests/tests.nim are against the new API, all the others are for the old API (which resembles more the Ethereum Foundation tests).
The text was updated successfully, but these errors were encountered: