diff --git a/.gitignore b/.gitignore index 450b587..f581ab0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ nimble.paths ci/build_nim.sh ci/NimBinaries/ ci/nim/ + +*.exe diff --git a/secp256k1.nim b/secp256k1.nim index f3c7ddd..0a6394b 100644 --- a/secp256k1.nim +++ b/secp256k1.nim @@ -72,9 +72,6 @@ const SkEdchSecretSize* = 32 ## ECDH-agreed key size - SkEcdhRawSecretSize* = 33 - ## ECDH-agreed raw key size - type SkPublicKey* {.requiresInit.} = object ## Representation of public key. @@ -116,10 +113,7 @@ type ## Representation of ECDH shared secret data*: array[SkEdchSecretSize, byte] - SkEcdhRawSecret* {.requiresInit.} = object - ## Representation of ECDH shared secret, with leading `y` byte - # (`y` is 0x02 when pubkey.y is even or 0x03 when odd) - data*: array[SkEcdhRawSecretSize, byte] + SkEcdhHashFunc* = secp256k1_ecdh_hash_function SkResult*[T] = Result[T, cstring] @@ -607,26 +601,26 @@ func recover*(sig: SkRecoverableSignature, msg: SkMessage): SkResult[SkPublicKey ok(SkPublicKey(data: data)) -func ecdh*(seckey: SkSecretKey, pubkey: SkPublicKey): SkEcdhSecret = +func ecdh*(seckey: SkSecretKey, pubkey: SkPublicKey): SkResult[SkEcdhSecret] = ## Calculate ECDH shared secret. var secret {.noinit.}: array[SkEdchSecretSize, byte] - let res = secp256k1_ecdh( + if secp256k1_ecdh( secp256k1_context_no_precomp, secret.baseAddr, unsafeAddr pubkey.data, - seckey.data.baseAddr) - doAssert res == 1, "cannot compute ECDH secret, keys invalid?" + seckey.data.baseAddr) != 1: + return err("cannot compute ECDH secret, keys invalid?") - SkEcdhSecret(data: secret) + ok(SkEcdhSecret(data: secret)) -func ecdhRaw*(seckey: SkSecretKey, pubkey: SkPublicKey): SkEcdhRawSecret = - ## Calculate ECDH shared secret, ethereum style - # TODO - deprecate: https://github.com/status-im/nim-eth/issues/222 - var secret {.noinit.}: array[SkEcdhRawSecretSize, byte] - let res = secp256k1_ecdh_raw( - secp256k1_context_no_precomp, secret.baseAddr, unsafeAddr pubkey.data, - seckey.data.baseAddr) - doAssert res == 1, "cannot compute raw ECDH secret, keys invalid?" +func ecdh*[N: static[int]](seckey: SkSecretKey, pubkey: SkPublicKey, + hashfn: SkEcdhHashFunc, data: pointer): SkResult[array[N, byte]] = + ## Calculate ECDH shared secret using custom hash function. + var secret {.noinit.}: array[N, byte] + if secp256k1_ecdh( + secp256k1_context_no_precomp, secret.baseAddr, unsafeAddr pubkey.data, + seckey.data.baseAddr, hashfn, data) != 1: + return err("cannot compute ECDH secret, keys invalid?") - SkEcdhRawSecret(data: secret) + ok(secret) func clear*(v: var SkSecretKey) = ## Wipe and clear memory of Secp256k1 `private key`. @@ -640,12 +634,6 @@ func clear*(v: var SkEcdhSecret) = ## result in undefined behaviour or Defect burnMem(v.data) -func clear*(v: var SkEcdhRawSecret) = - ## Wipe and clear memory of ECDH `shared secret`. - ## After calling this function, the key is invalid and using it elsewhere will - ## result in undefined behaviour or Defect - burnMem(v.data) - func `$`*( v: SkPublicKey | SkSecretKey | SkXOnlyPublicKey | SkSignature | SkRecoverableSignature | SkSchnorrSignature): string = toHex(v) @@ -665,7 +653,6 @@ proc default*(T: type SkSignature): T {.error: "loophole".} proc default*(T: type SkRecoverableSignature): T {.error: "loophole".} proc default*(T: type SkSchnorrSignature): T {.error: "loophole".} proc default*(T: type SkEcdhSecret): T {.error: "loophole".} -proc default*(T: type SkEcdhRawSecret): T {.error: "loophole".} func tweakAdd*(secretKey: var SkSecretKey, tweak: openArray[byte]): SkResult[void] = let res = secp256k1_ec_privkey_tweak_add( diff --git a/secp256k1/abi.nim b/secp256k1/abi.nim index f129cf3..ee9f2ea 100644 --- a/secp256k1/abi.nim +++ b/secp256k1/abi.nim @@ -35,7 +35,7 @@ type secp256k1_ecdh_hash_function* = proc (output: ptr byte, x32, y32: ptr byte, - data: pointer) {.cdecl, raises: [].} + data: pointer): cint {.cdecl, raises: [].} secp256k1_context* = object secp256k1_scratch_space* = object @@ -312,23 +312,6 @@ template secp256k1_ecdh*(ctx: ptr secp256k1_context; output32: ptr byte; secp256k1_ecdh(ctx, output32, pubkey, privkey, secp256k1_ecdh_hash_function_default(), nil) -proc secp256k1_ecdh_raw*(ctx: ptr secp256k1_context; output32: ptr byte; - pubkey: ptr secp256k1_pubkey; - input32: ptr byte): cint {.secp.} - ## Compute an EC Diffie-Hellman secret in constant time - ## Returns: 1: exponentiation was successful - ## 0: scalar was invalid (zero or overflow) - ## Args: ctx: pointer to a context object (cannot be NULL) - ## Out: result: a 33-byte array which will be populated by an ECDH - ## secret computed from the point and scalar in form - ## of compressed point - ## In: pubkey: a pointer to a secp256k1_pubkey containing an - ## initialized public key - ## privkey: a 32-byte scalar with which to multiply the point - ## - -## Multikey interface follows - type secp256k1_xonly_pubkey* = object ## Opaque data structure that holds a parsed and valid "x-only" public key. diff --git a/tests/test_secp256k1.nim b/tests/test_secp256k1.nim index 7d727df..cf50e0e 100644 --- a/tests/test_secp256k1.nim +++ b/tests/test_secp256k1.nim @@ -1,4 +1,7 @@ -import ../secp256k1, unittest +import + ../secp256k1, + unittest, + stew/ptrops {.used.} @@ -15,13 +18,13 @@ const 1'u8, 0, 0, 0, 0, 0, 0, 0, 1'u8, 0, 0, 0, 0, 0, 0, 0, ]) - msg2 = array[40, byte]([ + msg2: array[40, byte] = [ 0'u8, 0, 0, 0, 0, 0, 0, 0, 0'u8, 0, 0, 0, 0, 0, 0, 0, 0'u8, 0, 0, 0, 0, 0, 0, 0, 0'u8, 0, 0, 0, 0, 0, 0, 0, 0'u8, 0, 0, 0, 0, 0, 0, 0, - ]) + ] proc workingRng(data: var openArray[byte]): bool = data[0] += 1 @@ -72,3 +75,34 @@ suite "secp256k1": SkMessage.fromBytes([]).isErr() SkMessage.fromBytes([0'u8]).isErr() SkMessage.fromBytes(array[32, byte](msg0)).isOk() + + test "Custom hash function": + proc customHash(output: ptr byte, x32, y32: ptr byte, data: pointer): cint + {.cdecl, raises: [].} = + # Save x and y as uncompressed public key + output[] = 0x04 + copyMem(output.offset(1), x32, 32) + copyMem(output.offset(33), y32, 32) + return 1 + + proc skone(_: type SkSecretKey): SkSecretKey = + # silence noisy warning: Cannot prove that 'result' is initialized. + result = SkSecretKey.random(workingRng)[] + var data: array[SkRawSecretKeySize, byte] + zeroMem(data[0].addr, data.len) + data[31] = 1 + copyMem(result.addr, data[0].addr, data.len) + + let + sone = SkSecretKey.skone() + sb32 = SkSecretKey.random(workingRng)[] + pk0 = sone.toPublicKey + pk1 = sb32.toPublicKey + + var + # compute using ECDH function with custom hash function + outputEcdh = ecdh[65](sb32, pk0, customHash, nil).get + # compute "explicitly" + pointSer = pk1.toRaw + + check equalMem(outputEcdh.addr, pointSer.addr, 65)