From 42348616d42ed7f2efc49fa0f92056cd6346a769 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Wed, 10 Jun 2020 20:38:29 +0200 Subject: [PATCH 01/15] Remove keccak native dependency. --- package.json | 2 +- src/account.ts | 23 ++++++++++++++++------- src/hash.ts | 22 ++++++++++++++++++---- src/signature.ts | 9 +++++---- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 7ec82335..b1bdd5d6 100644 --- a/package.json +++ b/package.json @@ -91,8 +91,8 @@ "@types/bn.js": "^4.11.3", "bn.js": "^4.11.0", "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", "ethjs-util": "0.1.6", - "keccak": "^2.0.0", "rlp": "^2.2.3", "secp256k1": "^3.0.1" }, diff --git a/src/account.ts b/src/account.ts index 79e8e0a5..d1264038 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,6 +1,11 @@ const assert = require('assert') +const { + privateKeyVerify, + publicKeyCreate, + publicKeyVerify, + publicKeyConvert, +} = require('ethereum-cryptography/shims/hdkey-secp256k1v3') const ethjsUtil = require('ethjs-util') -const secp256k1 = require('secp256k1') import BN = require('bn.js') import { toBuffer, addHexPrefix, zeros, bufferToHex, unpad } from './bytes' import { keccak, keccak256, rlphash } from './hash' @@ -123,7 +128,11 @@ export const isPrecompiled = function(address: Buffer | string): boolean { * Checks if the private key satisfies the rules of the curve secp256k1. */ export const isValidPrivate = function(privateKey: Buffer): boolean { - return secp256k1.privateKeyVerify(privateKey) + try { + return privateKeyVerify(privateKey) + } catch (e) { + return false + } } /** @@ -135,14 +144,14 @@ export const isValidPrivate = function(privateKey: Buffer): boolean { export const isValidPublic = function(publicKey: Buffer, sanitize: boolean = false): boolean { if (publicKey.length === 64) { // Convert to SEC1 for secp256k1 - return secp256k1.publicKeyVerify(Buffer.concat([Buffer.from([4]), publicKey])) + return publicKeyVerify(Buffer.concat([Buffer.from([4]), publicKey])) } if (!sanitize) { return false } - return secp256k1.publicKeyVerify(publicKey) + return publicKeyVerify(publicKey) } /** @@ -154,7 +163,7 @@ export const isValidPublic = function(publicKey: Buffer, sanitize: boolean = fal export const pubToAddress = function(pubKey: Buffer, sanitize: boolean = false): Buffer { pubKey = toBuffer(pubKey) if (sanitize && pubKey.length !== 64) { - pubKey = secp256k1.publicKeyConvert(pubKey, false).slice(1) + pubKey = publicKeyConvert(pubKey, false).slice(1) } assert(pubKey.length === 64) // Only take the lower 160bits of the hash @@ -177,7 +186,7 @@ export const privateToAddress = function(privateKey: Buffer): Buffer { export const privateToPublic = function(privateKey: Buffer): Buffer { privateKey = toBuffer(privateKey) // skip the type flag and use the X, Y points - return secp256k1.publicKeyCreate(privateKey, false).slice(1) + return publicKeyCreate(privateKey, false).slice(1) } /** @@ -186,7 +195,7 @@ export const privateToPublic = function(privateKey: Buffer): Buffer { export const importPublic = function(publicKey: Buffer): Buffer { publicKey = toBuffer(publicKey) if (publicKey.length !== 64) { - publicKey = secp256k1.publicKeyConvert(publicKey, false).slice(1) + publicKey = publicKeyConvert(publicKey, false).slice(1) } return publicKey } diff --git a/src/hash.ts b/src/hash.ts index ea837906..d31ef312 100644 --- a/src/hash.ts +++ b/src/hash.ts @@ -1,4 +1,4 @@ -const createKeccakHash = require('keccak') +const { keccak224, keccak384, keccak256: k256, keccak512 } = require('ethereum-cryptography/keccak') const createHash = require('create-hash') const ethjsUtil = require('ethjs-util') import rlp = require('rlp') @@ -19,9 +19,23 @@ export const keccak = function(a: any, bits: number = 256): Buffer { if (!bits) bits = 256 - return createKeccakHash(`keccak${bits}`) - .update(a) - .digest() + switch (bits) { + case 224: { + return keccak224(a) + } + case 256: { + return k256(a) + } + case 384: { + return keccak384(a) + } + case 512: { + return keccak512(a) + } + default: { + throw new Error(`Invald algorithm: keccak${bits}`) + } + } } /** diff --git a/src/signature.ts b/src/signature.ts index 0149b6ba..af76ba16 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -1,4 +1,5 @@ -const secp256k1 = require('secp256k1') +const { sign, publicKeyConvert } = require('ethereum-cryptography/shims/hdkey-secp256k1v3') +const { ecdsaRecover } = require('ethereum-cryptography/secp256k1') import BN = require('bn.js') import { toBuffer, setLength, setLengthLeft, bufferToHex } from './bytes' import { keccak } from './hash' @@ -17,7 +18,7 @@ export const ecsign = function( privateKey: Buffer, chainId?: number, ): ECDSASignature { - const sig = secp256k1.sign(msgHash, privateKey) + const sig = sign(msgHash, privateKey) const recovery: number = sig.recovery const ret = { @@ -45,8 +46,8 @@ export const ecrecover = function( if (!isValidSigRecovery(recovery)) { throw new Error('Invalid signature v value') } - const senderPubKey = secp256k1.recover(msgHash, signature, recovery) - return secp256k1.publicKeyConvert(senderPubKey, false).slice(1) + const senderPubKey = ecdsaRecover(signature, recovery, msgHash) + return publicKeyConvert(senderPubKey, false).slice(1) } /** From e17e17460d20f0ffe627a213a344bc526f165101 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Tue, 16 Jun 2020 20:40:57 +0200 Subject: [PATCH 02/15] Remove secp256k1 native dependency. --- package.json | 3 +-- src/index.ts | 2 +- test/index.js | 28 +++++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b1bdd5d6..d4070795 100644 --- a/package.json +++ b/package.json @@ -93,8 +93,7 @@ "create-hash": "^1.1.2", "ethereum-cryptography": "^0.1.3", "ethjs-util": "0.1.6", - "rlp": "^2.2.3", - "secp256k1": "^3.0.1" + "rlp": "^2.2.3" }, "devDependencies": { "@ethereumjs/config-prettier": "^1.1.0", diff --git a/src/index.ts b/src/index.ts index c44af02e..663d3194 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -const secp256k1 = require('secp256k1') +const secp256k1 = require('ethereum-cryptography/secp256k1') const ethjsUtil = require('ethjs-util') import BN = require('bn.js') import rlp = require('rlp') diff --git a/test/index.js b/test/index.js index 9ae9b96f..fefe7e11 100644 --- a/test/index.js +++ b/test/index.js @@ -75,12 +75,38 @@ describe('is zero address', function () { }) describe('keccak', function () { - it('should produce a hash', function () { + it('should produce a keccak224 hash', function() { + const msg = '0x3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1' + const r = '9e66938bd8f32c8610444bb524630db496bd58b689f9733182df63ba' + const hash = ethUtils.keccak(msg, 224) + assert.equal(hash.toString('hex'), r) + }) + it('should produce a keccak256 hash', function() { const msg = '0x3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1' const r = '82ff40c0a986c6a5cfad4ddf4c3aa6996f1a7837f9c398e17e5de5cbd5a12b28' const hash = ethUtils.keccak(msg) assert.equal(hash.toString('hex'), r) }) + it('should produce a keccak384 hash', function() { + const msg = '0x3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1' + const r = + '923e0f6a1c324a698139c3f3abbe88ac70bf2e7c02b26192c6124732555a32cef18e81ac91d5d97ce969745409c5bbc6' + const hash = ethUtils.keccak(msg, 384) + assert.equal(hash.toString('hex'), r) + }) + it('should produce a keccak512 hash', function() { + const msg = '0x3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1' + const r = + '36fdacd0339307068e9ed191773a6f11f6f9f99016bd50f87fd529ab7c87e1385f2b7ef1ac257cc78a12dcb3e5804254c6a7b404a6484966b831eadc721c3d24' + const hash = ethUtils.keccak(msg, 512) + assert.equal(hash.toString('hex'), r) + }) + it('should error if provided incorrect bits', function() { + const msg = '0x3c9229289a6125f7fdf1885a77bb12c37a8d3b4962d936f7e3084dece32a3ca1' + assert.throws(function() { + ethUtils.keccak(msg, 1024) + }) + }) }) describe('keccak256', function () { From 498fa1c2388bfb200d7c988c978a6c6e39e6f748 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Fri, 19 Jun 2020 21:13:44 +0200 Subject: [PATCH 03/15] Use secp256k1 lib instead of shims. --- src/account.ts | 8 ++++---- src/index.ts | 1 + src/signature.ts | 13 ++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/account.ts b/src/account.ts index d1264038..1830e4ba 100644 --- a/src/account.ts +++ b/src/account.ts @@ -4,7 +4,7 @@ const { publicKeyCreate, publicKeyVerify, publicKeyConvert, -} = require('ethereum-cryptography/shims/hdkey-secp256k1v3') +} = require('ethereum-cryptography/secp256k1') const ethjsUtil = require('ethjs-util') import BN = require('bn.js') import { toBuffer, addHexPrefix, zeros, bufferToHex, unpad } from './bytes' @@ -163,7 +163,7 @@ export const isValidPublic = function(publicKey: Buffer, sanitize: boolean = fal export const pubToAddress = function(pubKey: Buffer, sanitize: boolean = false): Buffer { pubKey = toBuffer(pubKey) if (sanitize && pubKey.length !== 64) { - pubKey = publicKeyConvert(pubKey, false).slice(1) + pubKey = Buffer.from(publicKeyConvert(pubKey, false)).slice(1) } assert(pubKey.length === 64) // Only take the lower 160bits of the hash @@ -186,7 +186,7 @@ export const privateToAddress = function(privateKey: Buffer): Buffer { export const privateToPublic = function(privateKey: Buffer): Buffer { privateKey = toBuffer(privateKey) // skip the type flag and use the X, Y points - return publicKeyCreate(privateKey, false).slice(1) + return Buffer.from(publicKeyCreate(privateKey, false)).slice(1) } /** @@ -195,7 +195,7 @@ export const privateToPublic = function(privateKey: Buffer): Buffer { export const importPublic = function(publicKey: Buffer): Buffer { publicKey = toBuffer(publicKey) if (publicKey.length !== 64) { - publicKey = publicKeyConvert(publicKey, false).slice(1) + publicKey = Buffer.from(publicKeyConvert(publicKey, false)).slice(1) } return publicKey } diff --git a/src/index.ts b/src/index.ts index 663d3194..86ef997c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ export { rlp } /** * [`secp256k1`](https://github.com/cryptocoinjs/secp256k1-node/) */ +// TODO: create a wrapper export { secp256k1 } /** diff --git a/src/signature.ts b/src/signature.ts index af76ba16..6b8e3454 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -1,5 +1,4 @@ -const { sign, publicKeyConvert } = require('ethereum-cryptography/shims/hdkey-secp256k1v3') -const { ecdsaRecover } = require('ethereum-cryptography/secp256k1') +const { ecdsaSign, publicKeyConvert, ecdsaRecover } = require('ethereum-cryptography/secp256k1') import BN = require('bn.js') import { toBuffer, setLength, setLengthLeft, bufferToHex } from './bytes' import { keccak } from './hash' @@ -18,12 +17,12 @@ export const ecsign = function( privateKey: Buffer, chainId?: number, ): ECDSASignature { - const sig = sign(msgHash, privateKey) - const recovery: number = sig.recovery + const sig = ecdsaSign(msgHash, privateKey) + const recovery: number = sig.recid const ret = { - r: sig.signature.slice(0, 32), - s: sig.signature.slice(32, 64), + r: Buffer.from(sig.signature).slice(0, 32), + s: Buffer.from(sig.signature).slice(32, 64), v: chainId ? recovery + (chainId * 2 + 35) : recovery + 27, } @@ -47,7 +46,7 @@ export const ecrecover = function( throw new Error('Invalid signature v value') } const senderPubKey = ecdsaRecover(signature, recovery, msgHash) - return publicKeyConvert(senderPubKey, false).slice(1) + return Buffer.from(publicKeyConvert(senderPubKey, false)).slice(1) } /** From 79d7a2b9bccc380d2edf6fb95f49b91add77db5c Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Mon, 22 Jun 2020 22:40:33 +0200 Subject: [PATCH 04/15] Wrapper around secp256k1 to support v3 API. --- src/crypto.ts | 244 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 3 +- 2 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 src/crypto.ts diff --git a/src/crypto.ts b/src/crypto.ts new file mode 100644 index 00000000..988bc8e8 --- /dev/null +++ b/src/crypto.ts @@ -0,0 +1,244 @@ +//TODO: tests +import * as secp256k1 from 'secp256k1' +import BN = require('bn.js') + +const n = new BN( + Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 'hex'), +) + +export interface SignOptions { + data?: Buffer + noncefn?: ( + message: Uint8Array, + privateKey: Uint8Array, + algo: Uint8Array | null, + data: Uint8Array | null, + attempt: number, + ) => Uint8Array +} + +interface SigObj { + r: Buffer + s: Buffer +} + +export const privateKeyVerify = function(privateKey: Buffer): boolean { + return secp256k1.privateKeyVerify(privateKey) +} + +export const privateKeyExport = function(privateKey: Buffer, compressed?: boolean): Buffer { + return Buffer.from(secp256k1.privateKeyExport(privateKey, compressed)) +} + +export const privateKeyNegate = function(privateKey: Buffer): Buffer { + return Buffer.from(secp256k1.privateKeyNegate(privateKey)) +} + +export const privateKeyModInverse = function(privateKey: Buffer): Buffer { + return Buffer.from(secp256k1.privateKeyModInverse(privateKey)) +} + +export const privateKeyTweakAdd = function(privateKey: Buffer, tweak: Buffer): Buffer { + return Buffer.from(secp256k1.privateKeyTweakAdd(privateKey, tweak)) +} + +export const privateKeyTweakMul = function(privateKey: Buffer, tweak: Buffer): Buffer { + return Buffer.from(secp256k1.privateKeyTweakMul(privateKey, tweak)) +} + +export const publicKeyCreate = function(privateKey: Buffer, compressed?: boolean): Buffer { + return Buffer.from(secp256k1.publicKeyCreate(privateKey, compressed)) +} + +export const publicKeyConvert = function(publicKey: Buffer, compressed?: boolean): Buffer { + return Buffer.from(secp256k1.publicKeyConvert(publicKey, compressed)) +} + +export const publicKeyVerify = function(publicKey: Buffer): boolean { + return secp256k1.publicKeyVerify(publicKey) +} + +export const publicKeyTweakAdd = function( + publicKey: Buffer, + tweak: Buffer, + compressed?: boolean, +): Buffer { + return Buffer.from(secp256k1.publicKeyTweakAdd(publicKey, tweak, compressed)) +} + +export const publicKeyTweakMul = function( + publicKey: Buffer, + tweak: Buffer, + compressed?: boolean, +): Buffer { + return Buffer.from(secp256k1.publicKeyTweakMul(publicKey, tweak, compressed)) +} + +export const publicKeyCombine = function(publicKeys: Buffer[], compressed?: boolean): Buffer { + return Buffer.from(secp256k1.publicKeyCombine(publicKeys, compressed)) +} + +export const signatureNormalize = function(signature: Buffer): Buffer { + return Buffer.from(secp256k1.signatureNormalize(signature)) +} + +export const signatureExport = function(signature: Buffer): Buffer { + return Buffer.from(secp256k1.signatureExport(signature)) +} + +export const signatureImport = function(signature: Buffer): Buffer { + return Buffer.from(secp256k1.signatureImport(signature)) +} + +export const signatureImportLax = function(signature: Buffer): Buffer { + if (signature.length === 0) { + throw new RangeError('signature length is invalid') + } + + const sigObj = importLax(signature) + if (sigObj === null) { + throw new Error("couldn't parse DER signature") + } + + let r = new BN(sigObj.r) + if (r.ucmp(n)) { + r = new BN(0) + } + + let s = new BN(sigObj.s) + if (s.ucmp(n)) { + s = new BN(0) + } + + return Buffer.concat([r.toBuffer(), s.toBuffer()]) +} + +export const sign = function( + message: Buffer, + privateKey: Buffer, + options?: SignOptions, +): { signature: Buffer; recovery: number } { + const sig = secp256k1.ecdsaSign(message, privateKey, options) + + return { + signature: Buffer.from(sig.signature), + recovery: sig.recid, + } +} + +export const verify = function(message: Buffer, signature: Buffer, publicKey: Buffer): boolean { + return secp256k1.ecdsaVerify(signature, message, publicKey) +} + +export const recover = function( + signature: Buffer, + recid: number, + message: Buffer, + compressed?: boolean, +): Buffer { + return Buffer.from(secp256k1.ecdsaRecover(signature, recid, message, compressed)) +} + +export const ecdh = function(publicKey: Buffer, privateKey: Buffer): Buffer { + return Buffer.from(secp256k1.ecdh(publicKey, privateKey, {})) +} + +//TODO use compressed +export const ecdhUnsafe = function( + publicKey: Buffer, + privateKey: Buffer, + compressed?: boolean, +): Buffer { + const fn = function(x: Uint8Array, y: Uint8Array) { + const pubKey = new Uint8Array(33) + pubKey[0] = (y[31] & 1) === 0 ? 0x02 : 0x03 + pubKey.set(x, 1) + return pubKey + } + + return Buffer.from(secp256k1.ecdh(publicKey, privateKey, { hashfn: fn })) +} + +const importLax = function(signature: Buffer): SigObj | null { + const r = Buffer.alloc(32, 0) + const s = Buffer.alloc(32, 0) + + const length = signature.length + let index = 0 + + // sequence tag byte + if (signature[index++] !== 0x30) { + return null + } + + // sequence length byte + let lenbyte = signature[index++] + if (lenbyte & 0x80) { + index += lenbyte - 0x80 + if (index > length) { + return null + } + } + + // sequence tag byte for r + if (signature[index++] !== 0x02) { + return null + } + + // length for r + let rlen = signature[index++] + if (rlen & 0x80) { + lenbyte = rlen - 0x80 + if (index + lenbyte > length) { + return null + } + for (; lenbyte > 0 && signature[index] === 0x00; index += 1, lenbyte -= 1); + for (rlen = 0; lenbyte > 0; index += 1, lenbyte -= 1) rlen = (rlen << 8) + signature[index] + } + if (rlen > length - index) { + return null + } + let rindex = index + index += rlen + + // sequence tag byte for s + if (signature[index++] !== 0x02) { + return null + } + + // length for s + let slen = signature[index++] + if (slen & 0x80) { + lenbyte = slen - 0x80 + if (index + lenbyte > length) { + return null + } + for (; lenbyte > 0 && signature[index] === 0x00; index += 1, lenbyte -= 1); + for (slen = 0; lenbyte > 0; index += 1, lenbyte -= 1) slen = (slen << 8) + signature[index] + } + if (slen > length - index) { + return null + } + let sindex = index + index += slen + + // ignore leading zeros in r + for (; rlen > 0 && signature[rindex] === 0x00; rlen -= 1, rindex += 1); + // copy r value + if (rlen > 32) { + return null + } + const rvalue = signature.slice(rindex, rindex + rlen) + rvalue.copy(r, 32 - rvalue.length) + + // ignore leading zeros in s + for (; slen > 0 && signature[sindex] === 0x00; slen -= 1, sindex += 1); + // copy s value + if (slen > 32) { + return null + } + const svalue = signature.slice(sindex, sindex + slen) + svalue.copy(s, 32 - svalue.length) + + return { r: r, s: s } +} diff --git a/src/index.ts b/src/index.ts index 86ef997c..34f93266 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -const secp256k1 = require('ethereum-cryptography/secp256k1') +const secp256k1 = require('./crypto') const ethjsUtil = require('ethjs-util') import BN = require('bn.js') import rlp = require('rlp') @@ -17,7 +17,6 @@ export { rlp } /** * [`secp256k1`](https://github.com/cryptocoinjs/secp256k1-node/) */ -// TODO: create a wrapper export { secp256k1 } /** From 91bdfce0acc455ad25357910c0a6dfcf281980d2 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Mon, 22 Jun 2020 22:47:07 +0200 Subject: [PATCH 05/15] Add missing function. --- src/crypto.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/crypto.ts b/src/crypto.ts index 988bc8e8..89acd74e 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -30,6 +30,10 @@ export const privateKeyExport = function(privateKey: Buffer, compressed?: boolea return Buffer.from(secp256k1.privateKeyExport(privateKey, compressed)) } +export const privateKeyImport = function (privateKey: Buffer): Buffer { + return Buffer.from(secp256k1.privateKeyImport(privateKey)) +} + export const privateKeyNegate = function(privateKey: Buffer): Buffer { return Buffer.from(secp256k1.privateKeyNegate(privateKey)) } From 60c83adbffb754ed265ee917179f1835d1aa27a7 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Mon, 22 Jun 2020 22:47:44 +0200 Subject: [PATCH 06/15] Add missing function. --- src/crypto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto.ts b/src/crypto.ts index 89acd74e..ed2cda5d 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -30,7 +30,7 @@ export const privateKeyExport = function(privateKey: Buffer, compressed?: boolea return Buffer.from(secp256k1.privateKeyExport(privateKey, compressed)) } -export const privateKeyImport = function (privateKey: Buffer): Buffer { +export const privateKeyImport = function(privateKey: Buffer): Buffer { return Buffer.from(secp256k1.privateKeyImport(privateKey)) } From 04a41cf4bbe7faaea4d51b6d1d4a5bf984b50b60 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Tue, 23 Jun 2020 16:08:36 +0200 Subject: [PATCH 07/15] Secp256k1 wrapper refactor and testing. --- src/crypto.ts | 200 +++---- src/lib/der.ts | 650 +++++++++++++++++++++ src/lib/secp256k1.ts | 60 ++ test/index.js | 1300 ++++++++++++++++++++++++++++++++++++++++++ test/util.js | 89 +++ 5 files changed, 2163 insertions(+), 136 deletions(-) create mode 100644 src/lib/der.ts create mode 100644 src/lib/secp256k1.ts create mode 100644 test/util.js diff --git a/src/crypto.ts b/src/crypto.ts index ed2cda5d..a724d434 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -1,10 +1,6 @@ -//TODO: tests import * as secp256k1 from 'secp256k1' -import BN = require('bn.js') - -const n = new BN( - Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 'hex'), -) +const wrapper = require('./lib/secp256k1') +const der = require('./lib/der') export interface SignOptions { data?: Buffer @@ -17,49 +13,65 @@ export interface SignOptions { ) => Uint8Array } -interface SigObj { - r: Buffer - s: Buffer -} - export const privateKeyVerify = function(privateKey: Buffer): boolean { - return secp256k1.privateKeyVerify(privateKey) + return secp256k1.privateKeyVerify(Uint8Array.from(privateKey)) } export const privateKeyExport = function(privateKey: Buffer, compressed?: boolean): Buffer { - return Buffer.from(secp256k1.privateKeyExport(privateKey, compressed)) + if (privateKey.length !== 32) { + throw new RangeError('private key length is invalid') + } + + const publicKey = wrapper.privateKeyExport(privateKey, compressed) + + return der.privateKeyExport(privateKey, publicKey, compressed) } export const privateKeyImport = function(privateKey: Buffer): Buffer { - return Buffer.from(secp256k1.privateKeyImport(privateKey)) + privateKey = der.privateKeyImport(privateKey) + if (privateKey !== null && privateKey.length === 32 && privateKeyVerify(privateKey)) { + return privateKey + } + + throw new Error("couldn't import from DER format") } export const privateKeyNegate = function(privateKey: Buffer): Buffer { - return Buffer.from(secp256k1.privateKeyNegate(privateKey)) + return Buffer.from(secp256k1.privateKeyNegate(Uint8Array.from(privateKey))) } export const privateKeyModInverse = function(privateKey: Buffer): Buffer { - return Buffer.from(secp256k1.privateKeyModInverse(privateKey)) + if (privateKey.length !== 32) { + throw new Error('private key length is invalid') + } + + return Buffer.from(wrapper.privateKeyModInverse(Uint8Array.from(privateKey))) } export const privateKeyTweakAdd = function(privateKey: Buffer, tweak: Buffer): Buffer { - return Buffer.from(secp256k1.privateKeyTweakAdd(privateKey, tweak)) + return Buffer.from(secp256k1.privateKeyTweakAdd(Uint8Array.from(privateKey), tweak)) } export const privateKeyTweakMul = function(privateKey: Buffer, tweak: Buffer): Buffer { - return Buffer.from(secp256k1.privateKeyTweakMul(privateKey, tweak)) + return Buffer.from( + secp256k1.privateKeyTweakMul(Uint8Array.from(privateKey), Uint8Array.from(tweak)), + ) } export const publicKeyCreate = function(privateKey: Buffer, compressed?: boolean): Buffer { - return Buffer.from(secp256k1.publicKeyCreate(privateKey, compressed)) + return Buffer.from(secp256k1.publicKeyCreate(Uint8Array.from(privateKey), compressed)) } export const publicKeyConvert = function(publicKey: Buffer, compressed?: boolean): Buffer { - return Buffer.from(secp256k1.publicKeyConvert(publicKey, compressed)) + return Buffer.from(secp256k1.publicKeyConvert(Uint8Array.from(publicKey), compressed)) } export const publicKeyVerify = function(publicKey: Buffer): boolean { - return secp256k1.publicKeyVerify(publicKey) + if (publicKey.length !== 33 && publicKey.length !== 65) { + return false + } + + return secp256k1.publicKeyVerify(Uint8Array.from(publicKey)) } export const publicKeyTweakAdd = function( @@ -67,7 +79,9 @@ export const publicKeyTweakAdd = function( tweak: Buffer, compressed?: boolean, ): Buffer { - return Buffer.from(secp256k1.publicKeyTweakAdd(publicKey, tweak, compressed)) + return Buffer.from( + secp256k1.publicKeyTweakAdd(Uint8Array.from(publicKey), Uint8Array.from(tweak), compressed), + ) } export const publicKeyTweakMul = function( @@ -75,23 +89,30 @@ export const publicKeyTweakMul = function( tweak: Buffer, compressed?: boolean, ): Buffer { - return Buffer.from(secp256k1.publicKeyTweakMul(publicKey, tweak, compressed)) + return Buffer.from( + secp256k1.publicKeyTweakMul(Uint8Array.from(publicKey), Uint8Array.from(tweak), compressed), + ) } export const publicKeyCombine = function(publicKeys: Buffer[], compressed?: boolean): Buffer { - return Buffer.from(secp256k1.publicKeyCombine(publicKeys, compressed)) + const keys: Uint8Array[] = [] + publicKeys.forEach((publicKey: Buffer) => { + keys.push(Uint8Array.from(publicKey)) + }) + + return Buffer.from(secp256k1.publicKeyCombine(keys, compressed)) } export const signatureNormalize = function(signature: Buffer): Buffer { - return Buffer.from(secp256k1.signatureNormalize(signature)) + return Buffer.from(secp256k1.signatureNormalize(Uint8Array.from(signature))) } export const signatureExport = function(signature: Buffer): Buffer { - return Buffer.from(secp256k1.signatureExport(signature)) + return Buffer.from(secp256k1.signatureExport(Uint8Array.from(signature))) } export const signatureImport = function(signature: Buffer): Buffer { - return Buffer.from(secp256k1.signatureImport(signature)) + return Buffer.from(secp256k1.signatureImport(Uint8Array.from(signature))) } export const signatureImportLax = function(signature: Buffer): Buffer { @@ -99,22 +120,12 @@ export const signatureImportLax = function(signature: Buffer): Buffer { throw new RangeError('signature length is invalid') } - const sigObj = importLax(signature) + const sigObj = der.signatureImportLax(signature) if (sigObj === null) { throw new Error("couldn't parse DER signature") } - let r = new BN(sigObj.r) - if (r.ucmp(n)) { - r = new BN(0) - } - - let s = new BN(sigObj.s) - if (s.ucmp(n)) { - s = new BN(0) - } - - return Buffer.concat([r.toBuffer(), s.toBuffer()]) + return wrapper.signatureImport(sigObj) } export const sign = function( @@ -122,7 +133,13 @@ export const sign = function( privateKey: Buffer, options?: SignOptions, ): { signature: Buffer; recovery: number } { - const sig = secp256k1.ecdsaSign(message, privateKey, options) + if (options != null) { + if (options.data != null && options.data.length != 32) { + throw new RangeError('options.data length is invalid') + } + } + + const sig = secp256k1.ecdsaSign(Uint8Array.from(message), Uint8Array.from(privateKey), options) return { signature: Buffer.from(sig.signature), @@ -131,20 +148,22 @@ export const sign = function( } export const verify = function(message: Buffer, signature: Buffer, publicKey: Buffer): boolean { - return secp256k1.ecdsaVerify(signature, message, publicKey) + return secp256k1.ecdsaVerify(Uint8Array.from(signature), Uint8Array.from(message), publicKey) } export const recover = function( + message: Buffer, signature: Buffer, recid: number, - message: Buffer, compressed?: boolean, ): Buffer { - return Buffer.from(secp256k1.ecdsaRecover(signature, recid, message, compressed)) + return Buffer.from( + secp256k1.ecdsaRecover(Uint8Array.from(signature), recid, Uint8Array.from(message), compressed), + ) } export const ecdh = function(publicKey: Buffer, privateKey: Buffer): Buffer { - return Buffer.from(secp256k1.ecdh(publicKey, privateKey, {})) + return Buffer.from(secp256k1.ecdh(Uint8Array.from(publicKey), Uint8Array.from(privateKey), {})) } //TODO use compressed @@ -153,96 +172,5 @@ export const ecdhUnsafe = function( privateKey: Buffer, compressed?: boolean, ): Buffer { - const fn = function(x: Uint8Array, y: Uint8Array) { - const pubKey = new Uint8Array(33) - pubKey[0] = (y[31] & 1) === 0 ? 0x02 : 0x03 - pubKey.set(x, 1) - return pubKey - } - - return Buffer.from(secp256k1.ecdh(publicKey, privateKey, { hashfn: fn })) -} - -const importLax = function(signature: Buffer): SigObj | null { - const r = Buffer.alloc(32, 0) - const s = Buffer.alloc(32, 0) - - const length = signature.length - let index = 0 - - // sequence tag byte - if (signature[index++] !== 0x30) { - return null - } - - // sequence length byte - let lenbyte = signature[index++] - if (lenbyte & 0x80) { - index += lenbyte - 0x80 - if (index > length) { - return null - } - } - - // sequence tag byte for r - if (signature[index++] !== 0x02) { - return null - } - - // length for r - let rlen = signature[index++] - if (rlen & 0x80) { - lenbyte = rlen - 0x80 - if (index + lenbyte > length) { - return null - } - for (; lenbyte > 0 && signature[index] === 0x00; index += 1, lenbyte -= 1); - for (rlen = 0; lenbyte > 0; index += 1, lenbyte -= 1) rlen = (rlen << 8) + signature[index] - } - if (rlen > length - index) { - return null - } - let rindex = index - index += rlen - - // sequence tag byte for s - if (signature[index++] !== 0x02) { - return null - } - - // length for s - let slen = signature[index++] - if (slen & 0x80) { - lenbyte = slen - 0x80 - if (index + lenbyte > length) { - return null - } - for (; lenbyte > 0 && signature[index] === 0x00; index += 1, lenbyte -= 1); - for (slen = 0; lenbyte > 0; index += 1, lenbyte -= 1) slen = (slen << 8) + signature[index] - } - if (slen > length - index) { - return null - } - let sindex = index - index += slen - - // ignore leading zeros in r - for (; rlen > 0 && signature[rindex] === 0x00; rlen -= 1, rindex += 1); - // copy r value - if (rlen > 32) { - return null - } - const rvalue = signature.slice(rindex, rindex + rlen) - rvalue.copy(r, 32 - rvalue.length) - - // ignore leading zeros in s - for (; slen > 0 && signature[sindex] === 0x00; slen -= 1, sindex += 1); - // copy s value - if (slen > 32) { - return null - } - const svalue = signature.slice(sindex, sindex + slen) - svalue.copy(s, 32 - svalue.length) - - return { r: r, s: s } + return Buffer.from(secp256k1.ecdh(Uint8Array.from(publicKey), Uint8Array.from(privateKey))) } diff --git a/src/lib/der.ts b/src/lib/der.ts new file mode 100644 index 00000000..79fe368e --- /dev/null +++ b/src/lib/der.ts @@ -0,0 +1,650 @@ +import { SigObj } from './secp256k1' + +const EC_PRIVKEY_EXPORT_DER_COMPRESSED = Buffer.from([ + // begin + 0x30, + 0x81, + 0xd3, + 0x02, + 0x01, + 0x01, + 0x04, + 0x20, + // private key + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + // middle + 0xa0, + 0x81, + 0x85, + 0x30, + 0x81, + 0x82, + 0x02, + 0x01, + 0x01, + 0x30, + 0x2c, + 0x06, + 0x07, + 0x2a, + 0x86, + 0x48, + 0xce, + 0x3d, + 0x01, + 0x01, + 0x02, + 0x21, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xfe, + 0xff, + 0xff, + 0xfc, + 0x2f, + 0x30, + 0x06, + 0x04, + 0x01, + 0x00, + 0x04, + 0x01, + 0x07, + 0x04, + 0x21, + 0x02, + 0x79, + 0xbe, + 0x66, + 0x7e, + 0xf9, + 0xdc, + 0xbb, + 0xac, + 0x55, + 0xa0, + 0x62, + 0x95, + 0xce, + 0x87, + 0x0b, + 0x07, + 0x02, + 0x9b, + 0xfc, + 0xdb, + 0x2d, + 0xce, + 0x28, + 0xd9, + 0x59, + 0xf2, + 0x81, + 0x5b, + 0x16, + 0xf8, + 0x17, + 0x98, + 0x02, + 0x21, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xfe, + 0xba, + 0xae, + 0xdc, + 0xe6, + 0xaf, + 0x48, + 0xa0, + 0x3b, + 0xbf, + 0xd2, + 0x5e, + 0x8c, + 0xd0, + 0x36, + 0x41, + 0x41, + 0x02, + 0x01, + 0x01, + 0xa1, + 0x24, + 0x03, + 0x22, + 0x00, + // public key + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +]) + +const EC_PRIVKEY_EXPORT_DER_UNCOMPRESSED = Buffer.from([ + // begin + 0x30, + 0x82, + 0x01, + 0x13, + 0x02, + 0x01, + 0x01, + 0x04, + 0x20, + // private key + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + // middle + 0xa0, + 0x81, + 0xa5, + 0x30, + 0x81, + 0xa2, + 0x02, + 0x01, + 0x01, + 0x30, + 0x2c, + 0x06, + 0x07, + 0x2a, + 0x86, + 0x48, + 0xce, + 0x3d, + 0x01, + 0x01, + 0x02, + 0x21, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xfe, + 0xff, + 0xff, + 0xfc, + 0x2f, + 0x30, + 0x06, + 0x04, + 0x01, + 0x00, + 0x04, + 0x01, + 0x07, + 0x04, + 0x41, + 0x04, + 0x79, + 0xbe, + 0x66, + 0x7e, + 0xf9, + 0xdc, + 0xbb, + 0xac, + 0x55, + 0xa0, + 0x62, + 0x95, + 0xce, + 0x87, + 0x0b, + 0x07, + 0x02, + 0x9b, + 0xfc, + 0xdb, + 0x2d, + 0xce, + 0x28, + 0xd9, + 0x59, + 0xf2, + 0x81, + 0x5b, + 0x16, + 0xf8, + 0x17, + 0x98, + 0x48, + 0x3a, + 0xda, + 0x77, + 0x26, + 0xa3, + 0xc4, + 0x65, + 0x5d, + 0xa4, + 0xfb, + 0xfc, + 0x0e, + 0x11, + 0x08, + 0xa8, + 0xfd, + 0x17, + 0xb4, + 0x48, + 0xa6, + 0x85, + 0x54, + 0x19, + 0x9c, + 0x47, + 0xd0, + 0x8f, + 0xfb, + 0x10, + 0xd4, + 0xb8, + 0x02, + 0x21, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xfe, + 0xba, + 0xae, + 0xdc, + 0xe6, + 0xaf, + 0x48, + 0xa0, + 0x3b, + 0xbf, + 0xd2, + 0x5e, + 0x8c, + 0xd0, + 0x36, + 0x41, + 0x41, + 0x02, + 0x01, + 0x01, + 0xa1, + 0x44, + 0x03, + 0x42, + 0x00, + // public key + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +]) + +exports.privateKeyExport = function( + privateKey: Buffer, + publicKey: Buffer, + compressed: boolean = true, +) { + const result = Buffer.from( + compressed ? EC_PRIVKEY_EXPORT_DER_COMPRESSED : EC_PRIVKEY_EXPORT_DER_UNCOMPRESSED, + ) + privateKey.copy(result, compressed ? 8 : 9) + publicKey.copy(result, compressed ? 181 : 214) + return result +} + +exports.privateKeyImport = function(privateKey: Buffer): Buffer | null { + const length = privateKey.length + + // sequence header + let index = 0 + if (length < index + 1 || privateKey[index] !== 0x30) return null + index += 1 + + // sequence length constructor + if (length < index + 1 || !(privateKey[index] & 0x80)) return null + + const lenb = privateKey[index] & 0x7f + index += 1 + if (lenb < 1 || lenb > 2) return null + if (length < index + lenb) return null + + // sequence length + const len = privateKey[index + lenb - 1] | (lenb > 1 ? privateKey[index + lenb - 2] << 8 : 0) + index += lenb + if (length < index + len) return null + + // sequence element 0: version number (=1) + if ( + length < index + 3 || + privateKey[index] !== 0x02 || + privateKey[index + 1] !== 0x01 || + privateKey[index + 2] !== 0x01 + ) { + return null + } + index += 3 + + // sequence element 1: octet string, up to 32 bytes + if ( + length < index + 2 || + privateKey[index] !== 0x04 || + privateKey[index + 1] > 0x20 || + length < index + 2 + privateKey[index + 1] + ) { + return null + } + + return privateKey.slice(index + 2, index + 2 + privateKey[index + 1]) +} + +exports.signatureImportLax = function(signature: Buffer): SigObj | null { + const r = Buffer.alloc(32, 0) + const s = Buffer.alloc(32, 0) + + const length = signature.length + let index = 0 + + // sequence tag byte + if (signature[index++] !== 0x30) { + return null + } + + // sequence length byte + let lenbyte = signature[index++] + if (lenbyte & 0x80) { + index += lenbyte - 0x80 + if (index > length) { + return null + } + } + + // sequence tag byte for r + if (signature[index++] !== 0x02) { + return null + } + + // length for r + let rlen = signature[index++] + if (rlen & 0x80) { + lenbyte = rlen - 0x80 + if (index + lenbyte > length) { + return null + } + for (; lenbyte > 0 && signature[index] === 0x00; index += 1, lenbyte -= 1); + for (rlen = 0; lenbyte > 0; index += 1, lenbyte -= 1) rlen = (rlen << 8) + signature[index] + } + if (rlen > length - index) { + return null + } + let rindex = index + index += rlen + + // sequence tag byte for s + if (signature[index++] !== 0x02) { + return null + } + + // length for s + let slen = signature[index++] + if (slen & 0x80) { + lenbyte = slen - 0x80 + if (index + lenbyte > length) { + return null + } + for (; lenbyte > 0 && signature[index] === 0x00; index += 1, lenbyte -= 1); + for (slen = 0; lenbyte > 0; index += 1, lenbyte -= 1) slen = (slen << 8) + signature[index] + } + if (slen > length - index) { + return null + } + let sindex = index + index += slen + + // ignore leading zeros in r + for (; rlen > 0 && signature[rindex] === 0x00; rlen -= 1, rindex += 1); + // copy r value + if (rlen > 32) { + return null + } + const rvalue = signature.slice(rindex, rindex + rlen) + rvalue.copy(r, 32 - rvalue.length) + + // ignore leading zeros in s + for (; slen > 0 && signature[sindex] === 0x00; slen -= 1, sindex += 1); + // copy s value + if (slen > 32) { + return null + } + const svalue = signature.slice(sindex, sindex + slen) + svalue.copy(s, 32 - svalue.length) + + return { r: r, s: s } +} diff --git a/src/lib/secp256k1.ts b/src/lib/secp256k1.ts new file mode 100644 index 00000000..dce98b8c --- /dev/null +++ b/src/lib/secp256k1.ts @@ -0,0 +1,60 @@ +import BN = require('bn.js') +const EC = require('elliptic').ec + +const ec = new EC('secp256k1') +const ecparams = ec.curve + +export interface SigObj { + r: Buffer + s: Buffer +} + +exports.privateKeyExport = function(privateKey: Buffer, compressed: boolean = true): Buffer { + const d = new BN(privateKey) + if (d.ucmp(ecparams.n) >= 0) { + throw new Error('private was invalid, try again') + } + + const point = ec.g.mul(d) + return toPublicKey(point.getX(), point.getY(), compressed) +} + +exports.privateKeyModInverse = function(privateKey: Buffer): Buffer { + const bn = new BN(privateKey) + if (bn.ucmp(ecparams.n) >= 0 || bn.isZero()) { + throw new Error('private key range is invalid') + } + + return bn.invm(ecparams.n).toBuffer() +} + +exports.signatureImport = function(sigObj: SigObj): Buffer { + let r = new BN(sigObj.r) + if (r.ucmp(ecparams.n) >= 0) { + r = new BN(0) + } + + let s = new BN(sigObj.s) + if (s.ucmp(ecparams.n) >= 0) { + s = new BN(0) + } + + return Buffer.concat([r.toBuffer(), s.toBuffer()]) +} + +const toPublicKey = function(x: BN, y: BN, compressed: boolean): Buffer { + let publicKey + + if (compressed) { + publicKey = Buffer.alloc(33) + publicKey[0] = y.isOdd() ? 0x03 : 0x02 + x.toBuffer().copy(publicKey, 1) + } else { + publicKey = Buffer.alloc(65) + publicKey[0] = 0x04 + x.toBuffer().copy(publicKey, 1) + y.toBuffer().copy(publicKey, 33) + } + + return publicKey +} diff --git a/test/index.js b/test/index.js index fefe7e11..3d91e346 100644 --- a/test/index.js +++ b/test/index.js @@ -2,6 +2,8 @@ const assert = require('assert') const ethUtils = require('../dist/index.js') const BN = require('bn.js') const eip1014Testdata = require('./testdata/eip1014Examples.json') +const util = require('./util') +const getRandomBytes = require('crypto').randomBytes describe('constants', function() { it('should match constants', function () { @@ -833,3 +835,1301 @@ describe('message sig', function () { }) }) }) + +describe('privateKeyVerify', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyVerify(null) + }) + }) + + it('invalid length', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyVerify(util.getPrivateKey().slice(1)) + }) + }) + + it('zero key', function () { + const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) + assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), false) + }) + + + it('equal to N', function () { + const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), false) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + privateKeys.forEach((privateKey) => { + assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), true) + }) + }) +}) + +describe('privateKeyExport', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyExport(null) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyExport(util.getPrivateKey().slice(1)) + }) + }) + + it('private key is invalid', function () { + assert.throws(function () { + const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyExport(privateKey) + }) + }) +}) + +describe('privateKeyImport', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyImport(null) + }) + }) + + it('invalid format', function () { + assert.throws(function () { + const buffer = Buffer.from([0x00]) + ethUtils.secp256k1.privateKeyImport(buffer) + }) + }) +}) + +describe('privateKeyExport/privateKeyImport', function () { + it('export/import', function() { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const der1 = ethUtils.secp256k1.privateKeyExport(privateKey, true) + const privateKey1 = ethUtils.secp256k1.privateKeyImport(der1) + assert.deepEqual(privateKey1[0], privateKey[0]) + + const der2 = ethUtils.secp256k1.privateKeyExport(privateKey, false) + const privateKey2 = ethUtils.secp256k1.privateKeyImport(der2) + assert.deepEqual(privateKey2, privateKey) + }) + }) +}) + +describe('privateKeyNegate', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyNegate(null) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyNegate(util.getPrivateKey().slice(1)) + }) + }) + + it('private key is 0', function () { + const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) + + const expected = Buffer.alloc(32) + const result = ethUtils.secp256k1.privateKeyNegate(privateKey) + assert.deepEqual(result, expected) + }) + + it('private key equal to N', function () { + const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + + const expected = Buffer.alloc(32) + const result = ethUtils.secp256k1.privateKeyNegate(privateKey) + assert.deepEqual(result, expected) + }) + + it('private key overflow', function () { + const privateKey = util.ec.curve.n.addn(10).toArrayLike(Buffer, 'be', 32) + + const expected = util.ec.curve.n.subn(10).toArrayLike(Buffer, 'be', 32) + const result = ethUtils.secp256k1.privateKeyNegate(privateKey) + assert.deepEqual(result, expected) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + privateKeys.forEach((privateKey) => { + const expected = util.ec.curve.n.sub(new BN(privateKey)) + const result = ethUtils.secp256k1.privateKeyNegate(privateKey) + + assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) + }) + }) +}) + +describe('privateKeyModInverse', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyModInverse(null) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + ethUtils.secp256k1.privateKeyModInverse(privateKey) + }) + }) + + it('private key is 0', function () { + assert.throws(function () { + const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyModInverse(privateKey) + }) + }) + + it('private key equal to N', function () { + assert.throws(function () { + const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyModInverse(privateKey) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + privateKeys.forEach((privateKey) => { + const expected = new BN(privateKey).invm(new BN(util.ec.curve.n.toArrayLike(Buffer, 'be', 32))) + const result = ethUtils.secp256k1.privateKeyModInverse(privateKey) + + assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) + }) + }) +}) + +describe('privateKeyTweakAdd', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + const tweak = util.getTweak() + ethUtils.secp256k1.privateKeyTweakAdd(null, tweak) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + const tweak = util.getTweak() + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + }) + + it('tweak should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, null) + }) + }) + + it('tweak length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.getTweak().slice(1) + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + }) + + it('tweak overflow', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + }) + + it('result is zero: (N - 1) + 1', function () { + assert.throws(function () { + const privateKey = util.ec.curve.n.sub(util.BN_ONE).toArrayLike(Buffer, 'be', 32) + const tweak = util.BN_ONE.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + const tweak = util.getTweak() + + privateKeys.forEach((privateKey) => { + const expected = new BN(privateKey).add(new BN(tweak)).mod(util.ec.curve.n) + if (expected.cmp(util.BN_ZERO) === 0) { + assert.throws(function () { + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + } else { + const result = ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) + } + }) + }) +}) + +describe('privateKeyTweakMul', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + const tweak = util.getPrivateKey() + ethUtils.secp256k1.privateKeyTweakMul(null, tweak) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + const tweak = util.getPrivateKey() + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + }) + + it('tweak should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.privateKeyTweakMul(privateKey, null) + }) + }) + + it('tweak length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.getTweak().slice(1) + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + }) + + it('tweak equal N', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + }) + + it('tweak is 0', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + const tweak = util.getTweak() + + privateKeys.forEach((privateKey) => { + if (new BN(tweak).cmp(util.BN_ZERO) === 0) { + assert.throws(function () { + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + } else { + const expected = new BN(privateKey).mul(new BN(tweak)).mod(util.ec.curve.n) + const result = ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) + } + }) + }) +}) + +describe('publicKeyCreate', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyCreate(null) + }) + }) + + it('invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + ethUtils.secp256k1.publicKeyCreate(privateKey) + }) + }) + + it('overflow', function () { + assert.throws(function () { + const privateKey = util.ec.curve.p.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyCreate(privateKey) + }) + }) + + it('equal zero', function () { + assert.throws(function () { + const privateKey = new BN(0).toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyCreate(privateKey) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.publicKeyCreate(privateKey, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const expected = util.getPublicKey(privateKey) + + const compressed = ethUtils.secp256k1.publicKeyCreate(privateKey, true) + assert.deepEqual(compressed, expected.compressed) + + const uncompressed = ethUtils.secp256k1.publicKeyCreate(privateKey, false) + assert.deepEqual(uncompressed, expected.uncompressed) + }) + }) +}) + +describe('publicKeyConvert', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyConvert(null) + }) + }) + + it('length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.publicKeyConvert(publicKey) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.publicKeyConvert(publicKey, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const expected = util.getPublicKey(privateKey) + + const compressed = ethUtils.secp256k1.publicKeyConvert(expected.uncompressed, true) + assert.deepEqual(compressed, expected.compressed) + + const uncompressed = ethUtils.secp256k1.publicKeyConvert(expected.uncompressed, false) + assert.deepEqual(uncompressed, expected.uncompressed) + }) + }) +}) + +describe('publicKeyVerify', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyVerify(null) + }) + }) + + it('invalid length', function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('invalid first byte', function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('x overflow (first byte is 0x03)', function () { + const publicKey = Buffer.concat([ + Buffer.from([ 0x03 ]), + util.ec.curve.p.toArrayLike(Buffer, 'be', 32) + ]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('x overflow', function () { + const publicKey = Buffer.concat([ + Buffer.from([ 0x04 ]), + util.ec.curve.p.toArrayLike(Buffer, 'be', 32) + ]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('y overflow', function () { + const publicKey = Buffer.concat([ + Buffer.from([ 0x04 ]), + Buffer.alloc(32), + util.ec.curve.p.toArrayLike(Buffer, 'be', 32) + ]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('y is even, first byte is 0x07', function () { + const publicKey = Buffer.concat([ + Buffer.from([ 0x07 ]), + Buffer.alloc(32), + util.ec.curve.p.subn(1).toArrayLike(Buffer, 'be', 32) + ]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('y**2 !== x*x*x + 7', function () { + const publicKey = Buffer.concat([Buffer.from([0x04]), util.getTweak(), util.getTweak()]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const expected = util.getPublicKey(privateKey) + + assert.equal(ethUtils.secp256k1.publicKeyVerify(expected.uncompressed), true) + assert.equal(ethUtils.secp256k1.publicKeyVerify(expected.uncompressed), true) + }) + }) +}) + +describe('publicKeyTweakAdd', function () { + it('public key should be a Buffer', function () { + assert.throws(function () { + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakAdd(null, tweak) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) + }) + }) + + it('public key is invalid (version is 0x01)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) + }) + }) + + it('tweak should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, null) + }) + }) + + it('tweak length length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.getTweak().slice(1) + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) + }) + }) + + it('tweak overflow', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) + }) + }) + + it('tweak produce infinity point', function () { + // G * 1 - G = 0 + assert.throws(function () { + const publicKey = Buffer.from(util.ec.g.encode(null, true)) + publicKey[0] = publicKey[0] ^ 0x01 // change sign of G + const tweak = new BN(1).toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, true) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const tweak = util.getTweak() + const publicPoint = util.ec.g.mul(new BN(privateKey)) + const publicKey = Buffer.from(publicPoint.encode(null, true)) + const expected = util.ec.g.mul(new BN(tweak)).add(publicPoint) + + const compressed = ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, true) + assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) + + const uncompressed = ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, false) + assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) + }) + }) +}) + +describe('publicKeyTweakMul', function () { + it('public key should be a Buffer', function () { + assert.throws(function () { + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakMul(null, tweak) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('public key is invalid (version is 0x01)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('tweak should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.publicKeyTweakMul(publicKey, null) + }) + }) + + it('tweak length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.getTweak().slice(1) + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('tweak is zero', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = new BN(0).toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('tweak overflow', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const tweak = util.getTweak() + const publicPoint = util.ec.g.mul(new BN(privateKey)) + const publicKey = Buffer.from(publicPoint.encode(null, true)) + const expected = util.ec.g.mul(new BN(tweak)).add(publicPoint) + + if (new BN(tweak).cmp(util.BN_ZERO) === 0) { + assert.throws(function () { + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + } else { + const expected = publicPoint.mul(tweak) + + const compressed = ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, true) + assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) + + const uncompressed = ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, false) + assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) + } + }) + }) +}) + +describe('publicKeyCombine', function () { + it('public keys should be an Array', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyCombine(null) + }) + }) + + it('public keys should have length greater that zero', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyCombine([]) + }) + }) + + it('public key should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyCombine([null]) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.publicKeyCombine([publicKey]) + }) + }) + + it('public key is invalid (version is 0x01)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + ethUtils.secp256k1.publicKeyCombine([publicKey]) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.publicKeyCombine([publicKey], null) + }) + }) + + it('P + (-P) = 0', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey1 = util.getPublicKey(privateKey).compressed + const publicKey2 = Buffer.from(publicKey1) + publicKey2[0] = publicKey2[0] ^ 0x01 + ethUtils.secp256k1.publicKeyCombine([publicKey1, publicKey2], true) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const cnt = 1 + Math.floor(Math.random() * 3) // 1 <= cnt <= 3 + const privateKeys = [] + while (privateKeys.length < cnt) privateKeys.push(util.getPrivateKey()) + const publicKeys = privateKeys.map(function (privateKey) { + return util.getPublicKey(privateKey).compressed + }) + + let expected = util.ec.g.mul(new BN(privateKeys[0])) + for (let i = 1; i < privateKeys.length; ++i) { + const publicPoint = util.ec.g.mul(new BN(privateKeys[i])) + expected = expected.add(publicPoint) + } + + const compressed = ethUtils.secp256k1.publicKeyCombine(publicKeys, true) + assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) + + const uncompressed = ethUtils.secp256k1.publicKeyCombine(publicKeys, false) + assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) + }) + }) +}) + +describe('signatureNormalize', function () { + it('signature should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureNormalize(null) + }) + }) + + it('invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey).slice(1) + ethUtils.secp256k1.signatureNormalize(signature) + }) + }) + + it('parse fail (r equal N)', function () { + assert.throws(function () { + const signature = Buffer.concat([ + util.ec.curve.n.toArrayLike(Buffer, 'be', 32), + util.BN_ONE.toArrayLike(Buffer, 'be', 32) + ]) + ethUtils.secp256k1.signatureNormalize(signature) + }) + }) + + it('normalize return same signature (s equal n/2)', function () { + const signature = Buffer.concat([ + util.BN_ONE.toArrayLike(Buffer, 'be', 32), + util.ec.nh.toArrayLike(Buffer, 'be', 32) + ]) + const result = ethUtils.secp256k1.signatureNormalize(signature) + assert.deepEqual(result, signature) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const message = util.getMessage() + + const sigObj = util.sign(message, privateKey) + const result = ethUtils.secp256k1.signatureNormalize(sigObj.signature) + assert.deepEqual(result, sigObj.signatureLowS) + }) + }) +}) + +describe('signatureExport', function () { + it('signature should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureExport(null) + }) + }) + + it('invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey).slice(1) + ethUtils.secp256k1.signatureExport(signature) + }) + }) + + it('parse fail (r equal N)', function () { + assert.throws(function () { + const signature = Buffer.concat([ + util.ec.n.toArrayLike(Buffer, 'be', 32), + util.BN_ONE.toArrayLike(Buffer, 'be', 32) + ]) + ethUtils.secp256k1.signatureExport(signature) + }) + }) + +}) + +describe('signatureImport', function () { + it('signature should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureImport(null) + }) + }) + + it('parse fail', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureImport(Buffer.alloc(1)) + }) + }) + + it('parse not bip66 signature', function () { + const signature = Buffer.from('308002204171936738571ff75ec0c56c010f339f1f6d510ba45ad936b0762b1b2162d8020220152670567fa3cc92a5ea1a6ead11741832f8aede5ca176f559e8a46bb858e3f6', 'hex') + assert.throws(function () { + ethUtils.secp256k1.signatureImport(signature) + }) + }) + +}) + +describe('signatureImportLax', function () { + it('signature should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureImportLax(null) + }) + }) + + it('parse fail', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureImportLax(Buffer.alloc(1)) + }) + }) + + it('parse not bip66 signature', function () { + const signature = Buffer.from('308002204171936738571ff75ec0c56c010f339f1f6d510ba45ad936b0762b1b2162d8020220152670567fa3cc92a5ea1a6ead11741832f8aede5ca176f559e8a46bb858e3f6', 'hex') + assert.doesNotThrow(function () { + ethUtils.secp256k1.signatureImportLax(signature) + }) + }) +}) + +describe('signatureExport/signatureImport', function () { + it('signature should be a Buffer', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const message = util.getMessage() + + const signature = util.sign(message, privateKey).signatureLowS + + const der = ethUtils.secp256k1.signatureExport(signature) + assert.deepEqual(ethUtils.secp256k1.signatureImport(der), signature) + assert.deepEqual(ethUtils.secp256k1.signatureImportLax(der), signature) + }) + }) +}) + +describe('ecdh', function () { + it('public key should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = null + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('invalid public key', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x00 + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('secret key should be a Buffer', function () { + assert.throws(function () { + const privateKey = null + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('secret key invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('secret key equal zero', function () { + assert.throws(function () { + const privateKey = util.ec.curve.zero.fromRed().toArrayLike(Buffer, 'be', 32) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('secret key equal N', function () { + assert.throws(function () { + const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey1, i) => { + const privateKey2 = util.getPrivateKey() + const publicKey1 = util.getPublicKey(privateKey1).compressed + const publicKey2 = util.getPublicKey(privateKey2).compressed + + const shared1 = ethUtils.secp256k1.ecdh(publicKey1, privateKey2) + const shared2 = ethUtils.secp256k1.ecdh(publicKey2, privateKey1) + assert.deepEqual(shared1, shared2) + }) + }) +}) + + +describe('ecdhUnsafeUnsafe', function () { + it('public key should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = null + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('invalid public key', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x00 + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('secret key should be a Buffer', function () { + assert.throws(function () { + const privateKey = null + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('secret key invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('secret key equal zero', function () { + assert.throws(function () { + const privateKey = util.ec.curve.zero.fromRed().toArrayLike(Buffer, 'be', 32) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('secret key equal N', function () { + assert.throws(function () { + const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey1, i) => { + const privateKey2 = util.getPrivateKey() + const publicKey1 = util.getPublicKey(privateKey1).compressed + const publicKey2 = util.getPublicKey(privateKey2).compressed + + const shared1 = ethUtils.secp256k1.ecdhUnsafe(publicKey1, privateKey2) + const shared2 = ethUtils.secp256k1.ecdhUnsafe(publicKey2, privateKey1) + assert.deepEqual(shared1, shared2) + }) + }) +}) + +describe('sign', function () { + it('message should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(null, privateKey) + }) + }) + + it('message invalid length', function () { + assert.throws(function () { + const message = util.getMessage().slice(1) + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(message, privateKey) + }) + }) + + it('private key should be a Buffer', function () { + assert.throws(function () { + const message = util.getMessage() + ethUtils.secp256k1.sign(message, null) + }) + }) + + it('private key invalid length', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey().slice(1) + ethUtils.secp256k1.sign(message, privateKey) + }) + }) + + it('private key is invalid', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.sign(message, privateKey) + }) + }) + + it('options should be an Object', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(message, privateKey, null) + }) + }) + + it('options.data should be a Buffer', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(message, privateKey, { data: null }) + }) + }) + + it('options.data length is invalid', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + const data = getRandomBytes(31) + ethUtils.secp256k1.sign(message, privateKey, { data: data }) + }) + }) + + it('options.noncefn should be a Function', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(message, privateKey, { noncefn: null }) + }) + }) + + it('noncefn return not a Buffer', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + const noncefn = function () { return null } + ethUtils.secp256k1.sign(message, privateKey, { noncefn: noncefn }) + }) + }) + + it('noncefn return Buffer with invalid length', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + const noncefn = function () { return getRandomBytes(31) } + ethUtils.secp256k1.sign(message, privateKey, { noncefn: noncefn }) + }) + }) + + it('check options.noncefn arguments', function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + const data = getRandomBytes(32) + const noncefn = function (message2, privateKey2, algo, data2, attempt) { + assert.deepEqual(message2, message) + assert.deepEqual(privateKey, privateKey) + assert.deepEqual(algo, null) + assert.deepEqual(data2, data) + assert.deepEqual(attempt, 0) + return getRandomBytes(32) + } + ethUtils.secp256k1.sign(message, privateKey, { data: data, noncefn: noncefn }) + }) + +}) + +describe('verify', function () { + it('message should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(null, signature, publicKey) + }) + }) + + it('message length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage().slice(1) + const signature = util.getSignature(message, privateKey) + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + + it('signature should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(message, null, publicKey) + }) + }) + + it('signature length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey).slice(1) + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + + it('signature is invalid (r equal N)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = Buffer.concat([ + util.ec.n.toArrayLike(Buffer, 'be', 32), + getRandomBytes(32) + ]) + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + + it('public key should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.verify(message, signature, null) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + + it('public key is invalid (version is 0x01)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + +}) + +describe('recover', function () { + it('message should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.recover(null, signature, 0) + }) + }) + + it('message length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage().slice(1) + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.recover(message, signature, 0) + }) + }) + + it('signature should be a Buffer', function () { + assert.throws(function () { + const message = util.getMessage() + ethUtils.secp256k1.recover(message, null, 0) + }) + }) + + it('signature length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey).slice(1) + ethUtils.secp256k1.recover(message, signature, 0) + }) + }) + + it('signature is invalid (r equal N)', function () { + assert.throws(function () { + const message = util.getMessage() + const signature = Buffer.concat([ + util.ec.n.toArrayLike(Buffer, 'be', 32), + getRandomBytes(32) + ]) + ethUtils.secp256k1.recover(message, signature, 0) + }) + }) + + it('recovery should be a Number', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.recover(message, signature, null) + }) + }) + + it('recovery is invalid (equal 4)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(privateKey, message) + ethUtils.secp256k1.recover(message, signature, 4) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.recover(message, signature, 0, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey, i) => { + const message = util.getMessage() + const publicKey = util.getPublicKey(privateKey) + const expected = util.sign(message, privateKey) + + const sigObj = ethUtils.secp256k1.sign(message, privateKey) + assert.deepEqual(sigObj.signature, expected.signatureLowS) + assert.deepEqual(sigObj.recovery, expected.recovery) + + const isValid = ethUtils.secp256k1.verify(message, sigObj.signature, publicKey.compressed) + assert.equal(isValid, true) + + const compressed = ethUtils.secp256k1.recover(message, sigObj.signature, sigObj.recovery, true) + assert.deepEqual(compressed, publicKey.compressed) + + const uncompressed = ethUtils.secp256k1.recover(message, sigObj.signature, sigObj.recovery, false) + assert.deepEqual(uncompressed, publicKey.uncompressed) + }) + }) +}) diff --git a/test/util.js b/test/util.js new file mode 100644 index 00000000..ab3aad6f --- /dev/null +++ b/test/util.js @@ -0,0 +1,89 @@ +const BN = require('bn.js') +const EC = require('elliptic').ec +const ec = new EC('secp256k1') +const getRandomBytes = require('crypto').randomBytes + +function getPrivateKeys(count) { + const privateKeys = [] + for (let i = 0; i < count; i++) { + privateKeys.push(getRandomBytes(32)) + } + + return privateKeys +} + +function getPrivateKey() { + return getRandomBytes(32) +} + +function getTweak() { + return getRandomBytes(32) +} + +function getMessage () { + return getRandomBytes(32) +} + +function getSignature (message, privateKey) { + return sign(message, privateKey).signatureLowS +} + +function getPublicKeys(privateKeys) { + const publicKeys = [] + privateKeys.forEach((privateKey) => { + const publicKey = ec.keyFromPrivate(privateKey).getPublic() + publicKeys.push({ + compressed: Buffer.from(publicKey.encode(null, true)), + uncompressed: Buffer.from(publicKey.encode(null, false)) + }) + }) + + return publicKeys +} + +function getPublicKey(privateKey) { + const publicKey = ec.keyFromPrivate(privateKey).getPublic() + return { + compressed: Buffer.from(publicKey.encode(null, true)), + uncompressed: Buffer.from(publicKey.encode(null, false)) + } +} + +function sign (message, privateKey) { + const ecSig = ec.sign(message, privateKey, { canonical: false }) + + const signature = Buffer.concat([ + ecSig.r.toArrayLike(Buffer, 'be', 32), + ecSig.s.toArrayLike(Buffer, 'be', 32) + ]) + let recovery = ecSig.recoveryParam + if (ecSig.s.cmp(ec.nh) === 1) { + ecSig.s = ec.n.sub(ecSig.s) + recovery ^= 1 + } + const signatureLowS = Buffer.concat([ + ecSig.r.toArrayLike(Buffer, 'be', 32), + ecSig.s.toArrayLike(Buffer, 'be', 32) + ]) + + return { + signature: signature, + signatureLowS: signatureLowS, + recovery: recovery + } +} + +module.exports = { + ec: ec, + BN_ZERO: new BN(0), + BN_ONE: new BN(1), + + getPrivateKeys: getPrivateKeys, + getPrivateKey: getPrivateKey, + getPublicKey: getPublicKey, + getTweak: getTweak, + getMessage: getMessage, + getSignature: getSignature, + + sign: sign, +} From b91d5138f35dc68e1820359210b1bf7534cbf75e Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Tue, 23 Jun 2020 17:02:50 +0200 Subject: [PATCH 08/15] Fix ecdhUnsafe function. --- src/crypto.ts | 15 ++++++++++++--- src/lib/secp256k1.ts | 19 +++++++++++++++++++ test/index.js | 2 +- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/crypto.ts b/src/crypto.ts index a724d434..f6c211e7 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -1,4 +1,4 @@ -import * as secp256k1 from 'secp256k1' +import * as secp256k1 from 'ethereum-cryptography/secp256k1' const wrapper = require('./lib/secp256k1') const der = require('./lib/der') @@ -166,11 +166,20 @@ export const ecdh = function(publicKey: Buffer, privateKey: Buffer): Buffer { return Buffer.from(secp256k1.ecdh(Uint8Array.from(publicKey), Uint8Array.from(privateKey), {})) } -//TODO use compressed export const ecdhUnsafe = function( publicKey: Buffer, privateKey: Buffer, compressed?: boolean, ): Buffer { - return Buffer.from(secp256k1.ecdh(Uint8Array.from(publicKey), Uint8Array.from(privateKey))) + if (publicKey.length !== 33 && publicKey.length !== 65) { + throw new RangeError('public key length is invalid') + } + + if (privateKey.length !== 32) { + throw new RangeError('private key length is invalid') + } + + return Buffer.from( + wrapper.ecdhUnsafe(Uint8Array.from(publicKey), Uint8Array.from(privateKey), compressed), + ) } diff --git a/src/lib/secp256k1.ts b/src/lib/secp256k1.ts index dce98b8c..594f640e 100644 --- a/src/lib/secp256k1.ts +++ b/src/lib/secp256k1.ts @@ -42,6 +42,25 @@ exports.signatureImport = function(sigObj: SigObj): Buffer { return Buffer.concat([r.toBuffer(), s.toBuffer()]) } +exports.ecdhUnsafe = function( + publicKey: Buffer, + privateKey: Buffer, + compressed: boolean = true, +): Buffer { + const point = ec.keyFromPublic(publicKey) + if (point === null) { + throw new Error('the public key could not be parsed or is invalid') + } + + const scalar = new BN(privateKey) + if (scalar.ucmp(ecparams.n) >= 0 || scalar.isZero()) { + throw new Error('scalar was invalid (zero or overflow)') + } + + const shared = point.pub.mul(scalar) + return toPublicKey(shared.getX(), shared.getY(), compressed) +} + const toPublicKey = function(x: BN, y: BN, compressed: boolean): Buffer { let publicKey diff --git a/test/index.js b/test/index.js index 3d91e346..9ce5f616 100644 --- a/test/index.js +++ b/test/index.js @@ -911,7 +911,7 @@ describe('privateKeyExport/privateKeyImport', function () { privateKeys.forEach((privateKey) => { const der1 = ethUtils.secp256k1.privateKeyExport(privateKey, true) const privateKey1 = ethUtils.secp256k1.privateKeyImport(der1) - assert.deepEqual(privateKey1[0], privateKey[0]) + assert.deepEqual(privateKey1, privateKey) const der2 = ethUtils.secp256k1.privateKeyExport(privateKey, false) const privateKey2 = ethUtils.secp256k1.privateKeyImport(der2) From c7124c7dca3110def5296addd64beb0d8885717c Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Tue, 23 Jun 2020 18:39:40 +0200 Subject: [PATCH 09/15] Fix browser tests. --- src/crypto.ts | 76 ++++++++++++++++++++++++++++++++++++++------ src/lib/secp256k1.ts | 10 +++--- test/index.js | 2 +- 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/src/crypto.ts b/src/crypto.ts index f6c211e7..21b92027 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -5,12 +5,12 @@ const der = require('./lib/der') export interface SignOptions { data?: Buffer noncefn?: ( - message: Uint8Array, - privateKey: Uint8Array, - algo: Uint8Array | null, - data: Uint8Array | null, + message: Buffer, + privateKey: Buffer, + algo: Buffer | null, + data: Buffer | null, attempt: number, - ) => Uint8Array + ) => Buffer } export const privateKeyVerify = function(privateKey: Buffer): boolean { @@ -133,13 +133,71 @@ export const sign = function( privateKey: Buffer, options?: SignOptions, ): { signature: Buffer; recovery: number } { - if (options != null) { - if (options.data != null && options.data.length != 32) { - throw new RangeError('options.data length is invalid') + if (options === null) { + throw new TypeError('options should be an Object') + } + + let signOptions: secp256k1.SignOptions | undefined = undefined + + if (options) { + signOptions = {} + + if (options.data === null) { + throw new TypeError('options.data should be a Buffer') + } + + if (options.data) { + if (options.data.length != 32) { + throw new RangeError('options.data length is invalid') + } + + signOptions.data = new Uint8Array(options.data) + } + + if (options.noncefn === null) { + throw new TypeError('options.noncefn should be a Function') + } + + if (options.noncefn) { + signOptions.noncefn = ( + message: Uint8Array, + privateKey: Uint8Array, + algo: Uint8Array | null, + data: Uint8Array | null, + attempt: number, + ) => { + let bufferAlgo: Buffer | null = null + if (algo != null) { + bufferAlgo = Buffer.from(algo) + } + + let bufferData: Buffer | null = null + if (data != null) { + bufferData = Buffer.from(data) + } + + let buffer: Buffer = new Buffer('') + + if (options.noncefn) { + buffer = options.noncefn( + Buffer.from(message), + Buffer.from(privateKey), + bufferAlgo, + bufferData, + attempt, + ) + } + + return new Uint8Array(buffer) + } } } - const sig = secp256k1.ecdsaSign(Uint8Array.from(message), Uint8Array.from(privateKey), options) + const sig = secp256k1.ecdsaSign( + Uint8Array.from(message), + Uint8Array.from(privateKey), + signOptions, + ) return { signature: Buffer.from(sig.signature), diff --git a/src/lib/secp256k1.ts b/src/lib/secp256k1.ts index 594f640e..24abf2ad 100644 --- a/src/lib/secp256k1.ts +++ b/src/lib/secp256k1.ts @@ -25,7 +25,7 @@ exports.privateKeyModInverse = function(privateKey: Buffer): Buffer { throw new Error('private key range is invalid') } - return bn.invm(ecparams.n).toBuffer() + return bn.invm(ecparams.n).toArrayLike(Buffer, 'be', 32) } exports.signatureImport = function(sigObj: SigObj): Buffer { @@ -39,7 +39,7 @@ exports.signatureImport = function(sigObj: SigObj): Buffer { s = new BN(0) } - return Buffer.concat([r.toBuffer(), s.toBuffer()]) + return Buffer.concat([r.toArrayLike(Buffer, 'be', 32), s.toArrayLike(Buffer, 'be', 32)]) } exports.ecdhUnsafe = function( @@ -67,12 +67,12 @@ const toPublicKey = function(x: BN, y: BN, compressed: boolean): Buffer { if (compressed) { publicKey = Buffer.alloc(33) publicKey[0] = y.isOdd() ? 0x03 : 0x02 - x.toBuffer().copy(publicKey, 1) + x.toArrayLike(Buffer, 'be', 32).copy(publicKey, 1) } else { publicKey = Buffer.alloc(65) publicKey[0] = 0x04 - x.toBuffer().copy(publicKey, 1) - y.toBuffer().copy(publicKey, 33) + x.toArrayLike(Buffer, 'be', 32).copy(publicKey, 1) + y.toArrayLike(Buffer, 'be', 32).copy(publicKey, 33) } return publicKey diff --git a/test/index.js b/test/index.js index 9ce5f616..b01cdf0d 100644 --- a/test/index.js +++ b/test/index.js @@ -1941,7 +1941,7 @@ describe('sign', function () { const data = getRandomBytes(32) const noncefn = function (message2, privateKey2, algo, data2, attempt) { assert.deepEqual(message2, message) - assert.deepEqual(privateKey, privateKey) + assert.deepEqual(privateKey2, privateKey) assert.deepEqual(algo, null) assert.deepEqual(data2, data) assert.deepEqual(attempt, 0) From d51ba6698546babfccfd889d814cf60a69bdd14d Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Tue, 23 Jun 2020 23:40:57 +0200 Subject: [PATCH 10/15] Bump test coverage. --- src/crypto.ts | 11 ++-------- src/lib/secp256k1.ts | 3 --- test/index.js | 49 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/crypto.ts b/src/crypto.ts index 21b92027..a31322c7 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -166,15 +166,8 @@ export const sign = function( data: Uint8Array | null, attempt: number, ) => { - let bufferAlgo: Buffer | null = null - if (algo != null) { - bufferAlgo = Buffer.from(algo) - } - - let bufferData: Buffer | null = null - if (data != null) { - bufferData = Buffer.from(data) - } + const bufferAlgo: Buffer | null = algo != null ? Buffer.from(algo) : null + const bufferData: Buffer | null = data != null ? Buffer.from(data) : null let buffer: Buffer = new Buffer('') diff --git a/src/lib/secp256k1.ts b/src/lib/secp256k1.ts index 24abf2ad..95337686 100644 --- a/src/lib/secp256k1.ts +++ b/src/lib/secp256k1.ts @@ -48,9 +48,6 @@ exports.ecdhUnsafe = function( compressed: boolean = true, ): Buffer { const point = ec.keyFromPublic(publicKey) - if (point === null) { - throw new Error('the public key could not be parsed or is invalid') - } const scalar = new BN(privateKey) if (scalar.ucmp(ecparams.n) >= 0 || scalar.isZero()) { diff --git a/test/index.js b/test/index.js index b01cdf0d..1cedf05a 100644 --- a/test/index.js +++ b/test/index.js @@ -646,6 +646,13 @@ describe('isValidSignature', function () { const v = 27 assert.equal(ethUtils.isValidSignature(v, r, s, true), false) }) + it('should fail when s is 0 bytes', function () { + const r = Buffer.from('99e71a99cb2270b8cac5254f9e99b6210c6c10224a1579cf389ef88b20a1abe9', 'hex') + const s = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') + + const v = 27 + assert.equal(ethUtils.isValidSignature(v, r, s, true), false) + }) it('should not fail when not on homestead but s > secp256k1n/2', function () { const SECP256K1_N_DIV_2 = new BN('7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0', 16) @@ -897,9 +904,20 @@ describe('privateKeyImport', function () { }) it('invalid format', function () { - assert.throws(function () { - const buffer = Buffer.from([0x00]) - ethUtils.secp256k1.privateKeyImport(buffer) + const buffers = [ + Buffer.from([0x00]), + Buffer.from([0x30, 0x7b]), + Buffer.from([0x30, 0x87]), + Buffer.from([0x30, 0x81]), + Buffer.from([0x30, 0x82, 0x00, 0xff]), + Buffer.from([0x30, 0x82, 0x00, 0x00]), + Buffer.from([0x30, 0x82, 0x00, 0x00, 0x02, 0x01, 0x01]) + ] + + buffers.forEach((buffer) => { + assert.throws(function () { + ethUtils.secp256k1.privateKeyImport(buffer) + }) }) }) }) @@ -916,6 +934,10 @@ describe('privateKeyExport/privateKeyImport', function () { const der2 = ethUtils.secp256k1.privateKeyExport(privateKey, false) const privateKey2 = ethUtils.secp256k1.privateKeyImport(der2) assert.deepEqual(privateKey2, privateKey) + + const der3 = ethUtils.secp256k1.privateKeyExport(privateKey) + const privateKey3 = ethUtils.secp256k1.privateKeyImport(der3) + assert.deepEqual(privateKey3, privateKey) }) }) }) @@ -1669,8 +1691,25 @@ describe('signatureImportLax', function () { }) it('parse fail', function () { - assert.throws(function () { - ethUtils.secp256k1.signatureImportLax(Buffer.alloc(1)) + const buffers = [ + Buffer.alloc(0), + Buffer.alloc(1), + Buffer.from([0x30, 0x7b]), + Buffer.from([0x30, 0x87]), + Buffer.from([0x30, 0x80, 0x02, 0x80]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01]), + Buffer.from([0x30, 0x82, 0x00, 0x00, 0x02, 0x01, 0x01]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01, 0x00, 0x02, 0x81]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01, 0x00, 0x02, 0x81, 0x01]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x21, 0x01, 0x00, 0x02, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x05, 0x01, 0x00, 0x02, 0x21, 0x02, 0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ] + + buffers.forEach((buffer) => { + assert.throws(function () { + ethUtils.secp256k1.signatureImportLax(buffer) + }) }) }) From ed841549eae4d9d6cb6436e978fd74cb30132214 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Wed, 1 Jul 2020 00:25:56 +0200 Subject: [PATCH 11/15] Handle invalid length of private key during validation. --- src/account.ts | 6 +++--- src/crypto.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/account.ts b/src/account.ts index 1830e4ba..4d0aa2e8 100644 --- a/src/account.ts +++ b/src/account.ts @@ -128,11 +128,11 @@ export const isPrecompiled = function(address: Buffer | string): boolean { * Checks if the private key satisfies the rules of the curve secp256k1. */ export const isValidPrivate = function(privateKey: Buffer): boolean { - try { - return privateKeyVerify(privateKey) - } catch (e) { + if (privateKey.length !== 32) { return false } + + return privateKeyVerify(privateKey) } /** diff --git a/src/crypto.ts b/src/crypto.ts index a31322c7..61ea42f1 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -169,7 +169,7 @@ export const sign = function( const bufferAlgo: Buffer | null = algo != null ? Buffer.from(algo) : null const bufferData: Buffer | null = data != null ? Buffer.from(data) : null - let buffer: Buffer = new Buffer('') + let buffer: Buffer = Buffer.from('') if (options.noncefn) { buffer = options.noncefn( From 02bc4c5a086e38f8414cccc021f834a5427c1edc Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Thu, 2 Jul 2020 23:21:36 +0200 Subject: [PATCH 12/15] Add elliptic package, file renaming and minor fixes. --- package.json | 1 + src/index.ts | 2 +- src/{crypto.ts => secp256k1v3-adapter.ts} | 4 ++-- src/{lib => secp256k1v3-lib}/der.ts | 5 ++++- src/{lib/secp256k1.ts => secp256k1v3-lib/index.ts} | 5 ++++- test/index.js | 2 +- test/util.js | 3 +++ 7 files changed, 16 insertions(+), 6 deletions(-) rename src/{crypto.ts => secp256k1v3-adapter.ts} (98%) rename src/{lib => secp256k1v3-lib}/der.ts (98%) rename src/{lib/secp256k1.ts => secp256k1v3-lib/index.ts} (91%) diff --git a/package.json b/package.json index d4070795..3c0d075c 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "bn.js": "^4.11.0", "create-hash": "^1.1.2", "ethereum-cryptography": "^0.1.3", + "elliptic": "^6.5.2", "ethjs-util": "0.1.6", "rlp": "^2.2.3" }, diff --git a/src/index.ts b/src/index.ts index 34f93266..a8cbc30d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -const secp256k1 = require('./crypto') +const secp256k1 = require('./secp256k1v3-adapter') const ethjsUtil = require('ethjs-util') import BN = require('bn.js') import rlp = require('rlp') diff --git a/src/crypto.ts b/src/secp256k1v3-adapter.ts similarity index 98% rename from src/crypto.ts rename to src/secp256k1v3-adapter.ts index 61ea42f1..ce604133 100644 --- a/src/crypto.ts +++ b/src/secp256k1v3-adapter.ts @@ -1,6 +1,6 @@ import * as secp256k1 from 'ethereum-cryptography/secp256k1' -const wrapper = require('./lib/secp256k1') -const der = require('./lib/der') +const wrapper = require('./secp256k1v3-lib/index') +const der = require('./secp256k1v3-lib/der') export interface SignOptions { data?: Buffer diff --git a/src/lib/der.ts b/src/secp256k1v3-lib/der.ts similarity index 98% rename from src/lib/der.ts rename to src/secp256k1v3-lib/der.ts index 79fe368e..946c05ce 100644 --- a/src/lib/der.ts +++ b/src/secp256k1v3-lib/der.ts @@ -1,4 +1,7 @@ -import { SigObj } from './secp256k1' +// This file is imported from secp256k1 v3 +// https://github.com/cryptocoinjs/secp256k1-node/blob/master/LICENSE + +import { SigObj } from './index' const EC_PRIVKEY_EXPORT_DER_COMPRESSED = Buffer.from([ // begin diff --git a/src/lib/secp256k1.ts b/src/secp256k1v3-lib/index.ts similarity index 91% rename from src/lib/secp256k1.ts rename to src/secp256k1v3-lib/index.ts index 95337686..210adc3c 100644 --- a/src/lib/secp256k1.ts +++ b/src/secp256k1v3-lib/index.ts @@ -1,3 +1,6 @@ +// This file is imported from secp256k1 v3 +// https://github.com/cryptocoinjs/secp256k1-node/blob/master/LICENSE + import BN = require('bn.js') const EC = require('elliptic').ec @@ -12,7 +15,7 @@ export interface SigObj { exports.privateKeyExport = function(privateKey: Buffer, compressed: boolean = true): Buffer { const d = new BN(privateKey) if (d.ucmp(ecparams.n) >= 0) { - throw new Error('private was invalid, try again') + throw new Error("couldn't export to DER format") } const point = ec.g.mul(d) diff --git a/test/index.js b/test/index.js index 1cedf05a..cf7ac3d1 100644 --- a/test/index.js +++ b/test/index.js @@ -1811,7 +1811,7 @@ describe('ecdh', function () { }) -describe('ecdhUnsafeUnsafe', function () { +describe('ecdhUnsafe', function () { it('public key should be a Buffer', function () { assert.throws(function () { const privateKey = util.getPrivateKey() diff --git a/test/util.js b/test/util.js index ab3aad6f..777e3b71 100644 --- a/test/util.js +++ b/test/util.js @@ -1,3 +1,6 @@ +// This file is imported from secp256k1 v3 +// https://github.com/cryptocoinjs/secp256k1-node/blob/master/LICENSE + const BN = require('bn.js') const EC = require('elliptic').ec const ec = new EC('secp256k1') From 0423e4f17b1f399c76c6a0c8deb90ca1d6011c31 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Fri, 3 Jul 2020 00:35:07 +0200 Subject: [PATCH 13/15] Switch to using secp256k1 adapter. --- src/account.ts | 23 +- src/secp256k1v3-adapter.ts | 14 +- src/signature.ts | 14 +- test/index.js | 1332 ----------------------------------- test/secp256k1-adapter.js | 1336 ++++++++++++++++++++++++++++++++++++ 5 files changed, 1359 insertions(+), 1360 deletions(-) create mode 100644 test/secp256k1-adapter.js diff --git a/src/account.ts b/src/account.ts index 4d0aa2e8..5c9e0be7 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,11 +1,6 @@ const assert = require('assert') -const { - privateKeyVerify, - publicKeyCreate, - publicKeyVerify, - publicKeyConvert, -} = require('ethereum-cryptography/secp256k1') const ethjsUtil = require('ethjs-util') +const secp256k1 = require('./secp256k1v3-adapter') import BN = require('bn.js') import { toBuffer, addHexPrefix, zeros, bufferToHex, unpad } from './bytes' import { keccak, keccak256, rlphash } from './hash' @@ -128,11 +123,7 @@ export const isPrecompiled = function(address: Buffer | string): boolean { * Checks if the private key satisfies the rules of the curve secp256k1. */ export const isValidPrivate = function(privateKey: Buffer): boolean { - if (privateKey.length !== 32) { - return false - } - - return privateKeyVerify(privateKey) + return secp256k1.privateKeyVerify(privateKey) } /** @@ -144,14 +135,14 @@ export const isValidPrivate = function(privateKey: Buffer): boolean { export const isValidPublic = function(publicKey: Buffer, sanitize: boolean = false): boolean { if (publicKey.length === 64) { // Convert to SEC1 for secp256k1 - return publicKeyVerify(Buffer.concat([Buffer.from([4]), publicKey])) + return secp256k1.publicKeyVerify(Buffer.concat([Buffer.from([4]), publicKey])) } if (!sanitize) { return false } - return publicKeyVerify(publicKey) + return secp256k1.publicKeyVerify(publicKey) } /** @@ -163,7 +154,7 @@ export const isValidPublic = function(publicKey: Buffer, sanitize: boolean = fal export const pubToAddress = function(pubKey: Buffer, sanitize: boolean = false): Buffer { pubKey = toBuffer(pubKey) if (sanitize && pubKey.length !== 64) { - pubKey = Buffer.from(publicKeyConvert(pubKey, false)).slice(1) + pubKey = secp256k1.publicKeyConvert(pubKey, false).slice(1) } assert(pubKey.length === 64) // Only take the lower 160bits of the hash @@ -186,7 +177,7 @@ export const privateToAddress = function(privateKey: Buffer): Buffer { export const privateToPublic = function(privateKey: Buffer): Buffer { privateKey = toBuffer(privateKey) // skip the type flag and use the X, Y points - return Buffer.from(publicKeyCreate(privateKey, false)).slice(1) + return secp256k1.publicKeyCreate(privateKey, false).slice(1) } /** @@ -195,7 +186,7 @@ export const privateToPublic = function(privateKey: Buffer): Buffer { export const importPublic = function(publicKey: Buffer): Buffer { publicKey = toBuffer(publicKey) if (publicKey.length !== 64) { - publicKey = Buffer.from(publicKeyConvert(publicKey, false)).slice(1) + publicKey = secp256k1.publicKeyConvert(publicKey, false).slice(1) } return publicKey } diff --git a/src/secp256k1v3-adapter.ts b/src/secp256k1v3-adapter.ts index ce604133..60e34dab 100644 --- a/src/secp256k1v3-adapter.ts +++ b/src/secp256k1v3-adapter.ts @@ -1,5 +1,5 @@ import * as secp256k1 from 'ethereum-cryptography/secp256k1' -const wrapper = require('./secp256k1v3-lib/index') +const secp256k1v3 = require('./secp256k1v3-lib/index') const der = require('./secp256k1v3-lib/der') export interface SignOptions { @@ -14,6 +14,10 @@ export interface SignOptions { } export const privateKeyVerify = function(privateKey: Buffer): boolean { + if (privateKey.length !== 32) { + return false + } + return secp256k1.privateKeyVerify(Uint8Array.from(privateKey)) } @@ -22,7 +26,7 @@ export const privateKeyExport = function(privateKey: Buffer, compressed?: boolea throw new RangeError('private key length is invalid') } - const publicKey = wrapper.privateKeyExport(privateKey, compressed) + const publicKey = secp256k1v3.privateKeyExport(privateKey, compressed) return der.privateKeyExport(privateKey, publicKey, compressed) } @@ -45,7 +49,7 @@ export const privateKeyModInverse = function(privateKey: Buffer): Buffer { throw new Error('private key length is invalid') } - return Buffer.from(wrapper.privateKeyModInverse(Uint8Array.from(privateKey))) + return Buffer.from(secp256k1v3.privateKeyModInverse(Uint8Array.from(privateKey))) } export const privateKeyTweakAdd = function(privateKey: Buffer, tweak: Buffer): Buffer { @@ -125,7 +129,7 @@ export const signatureImportLax = function(signature: Buffer): Buffer { throw new Error("couldn't parse DER signature") } - return wrapper.signatureImport(sigObj) + return secp256k1v3.signatureImport(sigObj) } export const sign = function( @@ -231,6 +235,6 @@ export const ecdhUnsafe = function( } return Buffer.from( - wrapper.ecdhUnsafe(Uint8Array.from(publicKey), Uint8Array.from(privateKey), compressed), + secp256k1v3.ecdhUnsafe(Uint8Array.from(publicKey), Uint8Array.from(privateKey), compressed), ) } diff --git a/src/signature.ts b/src/signature.ts index 6b8e3454..5489360a 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -1,4 +1,4 @@ -const { ecdsaSign, publicKeyConvert, ecdsaRecover } = require('ethereum-cryptography/secp256k1') +const secp256k1 = require('./secp256k1v3-adapter') import BN = require('bn.js') import { toBuffer, setLength, setLengthLeft, bufferToHex } from './bytes' import { keccak } from './hash' @@ -17,12 +17,12 @@ export const ecsign = function( privateKey: Buffer, chainId?: number, ): ECDSASignature { - const sig = ecdsaSign(msgHash, privateKey) - const recovery: number = sig.recid + const sig = secp256k1.sign(msgHash, privateKey) + const recovery: number = sig.recovery const ret = { - r: Buffer.from(sig.signature).slice(0, 32), - s: Buffer.from(sig.signature).slice(32, 64), + r: sig.signature.slice(0, 32), + s: sig.signature.slice(32, 64), v: chainId ? recovery + (chainId * 2 + 35) : recovery + 27, } @@ -45,8 +45,8 @@ export const ecrecover = function( if (!isValidSigRecovery(recovery)) { throw new Error('Invalid signature v value') } - const senderPubKey = ecdsaRecover(signature, recovery, msgHash) - return Buffer.from(publicKeyConvert(senderPubKey, false)).slice(1) + const senderPubKey = secp256k1.recover(msgHash, signature, recovery) + return secp256k1.publicKeyConvert(senderPubKey, false).slice(1) } /** diff --git a/test/index.js b/test/index.js index cf7ac3d1..ee17179e 100644 --- a/test/index.js +++ b/test/index.js @@ -2,8 +2,6 @@ const assert = require('assert') const ethUtils = require('../dist/index.js') const BN = require('bn.js') const eip1014Testdata = require('./testdata/eip1014Examples.json') -const util = require('./util') -const getRandomBytes = require('crypto').randomBytes describe('constants', function() { it('should match constants', function () { @@ -842,1333 +840,3 @@ describe('message sig', function () { }) }) }) - -describe('privateKeyVerify', function () { - it('should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.privateKeyVerify(null) - }) - }) - - it('invalid length', function () { - assert.throws(function () { - ethUtils.secp256k1.privateKeyVerify(util.getPrivateKey().slice(1)) - }) - }) - - it('zero key', function () { - const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) - assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), false) - }) - - - it('equal to N', function () { - const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) - assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), false) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - privateKeys.forEach((privateKey) => { - assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), true) - }) - }) -}) - -describe('privateKeyExport', function () { - it('private key should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.privateKeyExport(null) - }) - }) - - it('private key length is invalid', function () { - assert.throws(function () { - ethUtils.secp256k1.privateKeyExport(util.getPrivateKey().slice(1)) - }) - }) - - it('private key is invalid', function () { - assert.throws(function () { - const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.privateKeyExport(privateKey) - }) - }) -}) - -describe('privateKeyImport', function () { - it('should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.privateKeyImport(null) - }) - }) - - it('invalid format', function () { - const buffers = [ - Buffer.from([0x00]), - Buffer.from([0x30, 0x7b]), - Buffer.from([0x30, 0x87]), - Buffer.from([0x30, 0x81]), - Buffer.from([0x30, 0x82, 0x00, 0xff]), - Buffer.from([0x30, 0x82, 0x00, 0x00]), - Buffer.from([0x30, 0x82, 0x00, 0x00, 0x02, 0x01, 0x01]) - ] - - buffers.forEach((buffer) => { - assert.throws(function () { - ethUtils.secp256k1.privateKeyImport(buffer) - }) - }) - }) -}) - -describe('privateKeyExport/privateKeyImport', function () { - it('export/import', function() { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const der1 = ethUtils.secp256k1.privateKeyExport(privateKey, true) - const privateKey1 = ethUtils.secp256k1.privateKeyImport(der1) - assert.deepEqual(privateKey1, privateKey) - - const der2 = ethUtils.secp256k1.privateKeyExport(privateKey, false) - const privateKey2 = ethUtils.secp256k1.privateKeyImport(der2) - assert.deepEqual(privateKey2, privateKey) - - const der3 = ethUtils.secp256k1.privateKeyExport(privateKey) - const privateKey3 = ethUtils.secp256k1.privateKeyImport(der3) - assert.deepEqual(privateKey3, privateKey) - }) - }) -}) - -describe('privateKeyNegate', function () { - it('private key should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.privateKeyNegate(null) - }) - }) - - it('private key length is invalid', function () { - assert.throws(function () { - ethUtils.secp256k1.privateKeyNegate(util.getPrivateKey().slice(1)) - }) - }) - - it('private key is 0', function () { - const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) - - const expected = Buffer.alloc(32) - const result = ethUtils.secp256k1.privateKeyNegate(privateKey) - assert.deepEqual(result, expected) - }) - - it('private key equal to N', function () { - const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) - - const expected = Buffer.alloc(32) - const result = ethUtils.secp256k1.privateKeyNegate(privateKey) - assert.deepEqual(result, expected) - }) - - it('private key overflow', function () { - const privateKey = util.ec.curve.n.addn(10).toArrayLike(Buffer, 'be', 32) - - const expected = util.ec.curve.n.subn(10).toArrayLike(Buffer, 'be', 32) - const result = ethUtils.secp256k1.privateKeyNegate(privateKey) - assert.deepEqual(result, expected) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - privateKeys.forEach((privateKey) => { - const expected = util.ec.curve.n.sub(new BN(privateKey)) - const result = ethUtils.secp256k1.privateKeyNegate(privateKey) - - assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) - }) - }) -}) - -describe('privateKeyModInverse', function () { - it('private key should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.privateKeyModInverse(null) - }) - }) - - it('private key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey().slice(1) - ethUtils.secp256k1.privateKeyModInverse(privateKey) - }) - }) - - it('private key is 0', function () { - assert.throws(function () { - const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.privateKeyModInverse(privateKey) - }) - }) - - it('private key equal to N', function () { - assert.throws(function () { - const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.privateKeyModInverse(privateKey) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - privateKeys.forEach((privateKey) => { - const expected = new BN(privateKey).invm(new BN(util.ec.curve.n.toArrayLike(Buffer, 'be', 32))) - const result = ethUtils.secp256k1.privateKeyModInverse(privateKey) - - assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) - }) - }) -}) - -describe('privateKeyTweakAdd', function () { - it('private key should be a Buffer', function () { - assert.throws(function () { - const tweak = util.getTweak() - ethUtils.secp256k1.privateKeyTweakAdd(null, tweak) - }) - }) - - it('private key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey().slice(1) - const tweak = util.getTweak() - ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) - }) - }) - - it('tweak should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - ethUtils.secp256k1.privateKeyTweakAdd(privateKey, null) - }) - }) - - it('tweak length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const tweak = util.getTweak().slice(1) - ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) - }) - }) - - it('tweak overflow', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) - }) - }) - - it('result is zero: (N - 1) + 1', function () { - assert.throws(function () { - const privateKey = util.ec.curve.n.sub(util.BN_ONE).toArrayLike(Buffer, 'be', 32) - const tweak = util.BN_ONE.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - const tweak = util.getTweak() - - privateKeys.forEach((privateKey) => { - const expected = new BN(privateKey).add(new BN(tweak)).mod(util.ec.curve.n) - if (expected.cmp(util.BN_ZERO) === 0) { - assert.throws(function () { - ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) - }) - } else { - const result = ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) - assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) - } - }) - }) -}) - -describe('privateKeyTweakMul', function () { - it('private key should be a Buffer', function () { - assert.throws(function () { - const tweak = util.getPrivateKey() - ethUtils.secp256k1.privateKeyTweakMul(null, tweak) - }) - }) - - it('private key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey().slice(1) - const tweak = util.getPrivateKey() - ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) - }) - }) - - it('tweak should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - ethUtils.secp256k1.privateKeyTweakMul(privateKey, null) - }) - }) - - it('tweak length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const tweak = util.getTweak().slice(1) - ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) - }) - }) - - it('tweak equal N', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) - }) - }) - - it('tweak is 0', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const tweak = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - const tweak = util.getTweak() - - privateKeys.forEach((privateKey) => { - if (new BN(tweak).cmp(util.BN_ZERO) === 0) { - assert.throws(function () { - ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) - }) - } else { - const expected = new BN(privateKey).mul(new BN(tweak)).mod(util.ec.curve.n) - const result = ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) - assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) - } - }) - }) -}) - -describe('publicKeyCreate', function () { - it('should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.publicKeyCreate(null) - }) - }) - - it('invalid length', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey().slice(1) - ethUtils.secp256k1.publicKeyCreate(privateKey) - }) - }) - - it('overflow', function () { - assert.throws(function () { - const privateKey = util.ec.curve.p.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.publicKeyCreate(privateKey) - }) - }) - - it('equal zero', function () { - assert.throws(function () { - const privateKey = new BN(0).toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.publicKeyCreate(privateKey) - }) - }) - - it('compressed should be a boolean', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - ethUtils.secp256k1.publicKeyCreate(privateKey, null) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const expected = util.getPublicKey(privateKey) - - const compressed = ethUtils.secp256k1.publicKeyCreate(privateKey, true) - assert.deepEqual(compressed, expected.compressed) - - const uncompressed = ethUtils.secp256k1.publicKeyCreate(privateKey, false) - assert.deepEqual(uncompressed, expected.uncompressed) - }) - }) -}) - -describe('publicKeyConvert', function () { - it('should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.publicKeyConvert(null) - }) - }) - - it('length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed.slice(1) - ethUtils.secp256k1.publicKeyConvert(publicKey) - }) - }) - - it('compressed should be a boolean', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.publicKeyConvert(publicKey, null) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const expected = util.getPublicKey(privateKey) - - const compressed = ethUtils.secp256k1.publicKeyConvert(expected.uncompressed, true) - assert.deepEqual(compressed, expected.compressed) - - const uncompressed = ethUtils.secp256k1.publicKeyConvert(expected.uncompressed, false) - assert.deepEqual(uncompressed, expected.uncompressed) - }) - }) -}) - -describe('publicKeyVerify', function () { - it('should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.publicKeyVerify(null) - }) - }) - - it('invalid length', function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed.slice(1) - assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) - }) - - it('invalid first byte', function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - publicKey[0] = 0x01 - assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) - }) - - it('x overflow (first byte is 0x03)', function () { - const publicKey = Buffer.concat([ - Buffer.from([ 0x03 ]), - util.ec.curve.p.toArrayLike(Buffer, 'be', 32) - ]) - assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) - }) - - it('x overflow', function () { - const publicKey = Buffer.concat([ - Buffer.from([ 0x04 ]), - util.ec.curve.p.toArrayLike(Buffer, 'be', 32) - ]) - assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) - }) - - it('y overflow', function () { - const publicKey = Buffer.concat([ - Buffer.from([ 0x04 ]), - Buffer.alloc(32), - util.ec.curve.p.toArrayLike(Buffer, 'be', 32) - ]) - assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) - }) - - it('y is even, first byte is 0x07', function () { - const publicKey = Buffer.concat([ - Buffer.from([ 0x07 ]), - Buffer.alloc(32), - util.ec.curve.p.subn(1).toArrayLike(Buffer, 'be', 32) - ]) - assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) - }) - - it('y**2 !== x*x*x + 7', function () { - const publicKey = Buffer.concat([Buffer.from([0x04]), util.getTweak(), util.getTweak()]) - assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const expected = util.getPublicKey(privateKey) - - assert.equal(ethUtils.secp256k1.publicKeyVerify(expected.uncompressed), true) - assert.equal(ethUtils.secp256k1.publicKeyVerify(expected.uncompressed), true) - }) - }) -}) - -describe('publicKeyTweakAdd', function () { - it('public key should be a Buffer', function () { - assert.throws(function () { - const tweak = util.getTweak() - ethUtils.secp256k1.publicKeyTweakAdd(null, tweak) - }) - }) - - it('public key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed.slice(1) - const tweak = util.getTweak() - ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) - }) - }) - - it('public key is invalid (version is 0x01)', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - publicKey[0] = 0x01 - const tweak = util.getTweak() - ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) - }) - }) - - it('tweak should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.publicKeyTweakAdd(publicKey, null) - }) - }) - - it('tweak length length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - const tweak = util.getTweak().slice(1) - ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) - }) - }) - - it('tweak overflow', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) - }) - }) - - it('tweak produce infinity point', function () { - // G * 1 - G = 0 - assert.throws(function () { - const publicKey = Buffer.from(util.ec.g.encode(null, true)) - publicKey[0] = publicKey[0] ^ 0x01 // change sign of G - const tweak = new BN(1).toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, true) - }) - }) - - it('compressed should be a boolean', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - const tweak = util.getTweak() - ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, null) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const tweak = util.getTweak() - const publicPoint = util.ec.g.mul(new BN(privateKey)) - const publicKey = Buffer.from(publicPoint.encode(null, true)) - const expected = util.ec.g.mul(new BN(tweak)).add(publicPoint) - - const compressed = ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, true) - assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) - - const uncompressed = ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, false) - assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) - }) - }) -}) - -describe('publicKeyTweakMul', function () { - it('public key should be a Buffer', function () { - assert.throws(function () { - const tweak = util.getTweak() - ethUtils.secp256k1.publicKeyTweakMul(null, tweak) - }) - }) - - it('public key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed.slice(1) - const tweak = util.getTweak() - ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) - }) - }) - - it('public key is invalid (version is 0x01)', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - publicKey[0] = 0x01 - const tweak = util.getTweak() - ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) - }) - }) - - it('tweak should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.publicKeyTweakMul(publicKey, null) - }) - }) - - it('tweak length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - const tweak = util.getTweak().slice(1) - ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) - }) - }) - - it('tweak is zero', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - const tweak = new BN(0).toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) - }) - }) - - it('tweak overflow', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) - }) - }) - - it('compressed should be a boolean', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - const tweak = util.getTweak() - ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, null) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const tweak = util.getTweak() - const publicPoint = util.ec.g.mul(new BN(privateKey)) - const publicKey = Buffer.from(publicPoint.encode(null, true)) - const expected = util.ec.g.mul(new BN(tweak)).add(publicPoint) - - if (new BN(tweak).cmp(util.BN_ZERO) === 0) { - assert.throws(function () { - ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) - }) - } else { - const expected = publicPoint.mul(tweak) - - const compressed = ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, true) - assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) - - const uncompressed = ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, false) - assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) - } - }) - }) -}) - -describe('publicKeyCombine', function () { - it('public keys should be an Array', function () { - assert.throws(function () { - ethUtils.secp256k1.publicKeyCombine(null) - }) - }) - - it('public keys should have length greater that zero', function () { - assert.throws(function () { - ethUtils.secp256k1.publicKeyCombine([]) - }) - }) - - it('public key should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.publicKeyCombine([null]) - }) - }) - - it('public key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed.slice(1) - ethUtils.secp256k1.publicKeyCombine([publicKey]) - }) - }) - - it('public key is invalid (version is 0x01)', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - publicKey[0] = 0x01 - ethUtils.secp256k1.publicKeyCombine([publicKey]) - }) - }) - - it('compressed should be a boolean', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.publicKeyCombine([publicKey], null) - }) - }) - - it('P + (-P) = 0', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey1 = util.getPublicKey(privateKey).compressed - const publicKey2 = Buffer.from(publicKey1) - publicKey2[0] = publicKey2[0] ^ 0x01 - ethUtils.secp256k1.publicKeyCombine([publicKey1, publicKey2], true) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const cnt = 1 + Math.floor(Math.random() * 3) // 1 <= cnt <= 3 - const privateKeys = [] - while (privateKeys.length < cnt) privateKeys.push(util.getPrivateKey()) - const publicKeys = privateKeys.map(function (privateKey) { - return util.getPublicKey(privateKey).compressed - }) - - let expected = util.ec.g.mul(new BN(privateKeys[0])) - for (let i = 1; i < privateKeys.length; ++i) { - const publicPoint = util.ec.g.mul(new BN(privateKeys[i])) - expected = expected.add(publicPoint) - } - - const compressed = ethUtils.secp256k1.publicKeyCombine(publicKeys, true) - assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) - - const uncompressed = ethUtils.secp256k1.publicKeyCombine(publicKeys, false) - assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) - }) - }) -}) - -describe('signatureNormalize', function () { - it('signature should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.signatureNormalize(null) - }) - }) - - it('invalid length', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey).slice(1) - ethUtils.secp256k1.signatureNormalize(signature) - }) - }) - - it('parse fail (r equal N)', function () { - assert.throws(function () { - const signature = Buffer.concat([ - util.ec.curve.n.toArrayLike(Buffer, 'be', 32), - util.BN_ONE.toArrayLike(Buffer, 'be', 32) - ]) - ethUtils.secp256k1.signatureNormalize(signature) - }) - }) - - it('normalize return same signature (s equal n/2)', function () { - const signature = Buffer.concat([ - util.BN_ONE.toArrayLike(Buffer, 'be', 32), - util.ec.nh.toArrayLike(Buffer, 'be', 32) - ]) - const result = ethUtils.secp256k1.signatureNormalize(signature) - assert.deepEqual(result, signature) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const message = util.getMessage() - - const sigObj = util.sign(message, privateKey) - const result = ethUtils.secp256k1.signatureNormalize(sigObj.signature) - assert.deepEqual(result, sigObj.signatureLowS) - }) - }) -}) - -describe('signatureExport', function () { - it('signature should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.signatureExport(null) - }) - }) - - it('invalid length', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey).slice(1) - ethUtils.secp256k1.signatureExport(signature) - }) - }) - - it('parse fail (r equal N)', function () { - assert.throws(function () { - const signature = Buffer.concat([ - util.ec.n.toArrayLike(Buffer, 'be', 32), - util.BN_ONE.toArrayLike(Buffer, 'be', 32) - ]) - ethUtils.secp256k1.signatureExport(signature) - }) - }) - -}) - -describe('signatureImport', function () { - it('signature should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.signatureImport(null) - }) - }) - - it('parse fail', function () { - assert.throws(function () { - ethUtils.secp256k1.signatureImport(Buffer.alloc(1)) - }) - }) - - it('parse not bip66 signature', function () { - const signature = Buffer.from('308002204171936738571ff75ec0c56c010f339f1f6d510ba45ad936b0762b1b2162d8020220152670567fa3cc92a5ea1a6ead11741832f8aede5ca176f559e8a46bb858e3f6', 'hex') - assert.throws(function () { - ethUtils.secp256k1.signatureImport(signature) - }) - }) - -}) - -describe('signatureImportLax', function () { - it('signature should be a Buffer', function () { - assert.throws(function () { - ethUtils.secp256k1.signatureImportLax(null) - }) - }) - - it('parse fail', function () { - const buffers = [ - Buffer.alloc(0), - Buffer.alloc(1), - Buffer.from([0x30, 0x7b]), - Buffer.from([0x30, 0x87]), - Buffer.from([0x30, 0x80, 0x02, 0x80]), - Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81]), - Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01]), - Buffer.from([0x30, 0x82, 0x00, 0x00, 0x02, 0x01, 0x01]), - Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01, 0x00, 0x02, 0x81]), - Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01, 0x00, 0x02, 0x81, 0x01]), - Buffer.from([0x30, 0x81, 0x00, 0x02, 0x21, 0x01, 0x00, 0x02, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02]), - Buffer.from([0x30, 0x81, 0x00, 0x02, 0x05, 0x01, 0x00, 0x02, 0x21, 0x02, 0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) - ] - - buffers.forEach((buffer) => { - assert.throws(function () { - ethUtils.secp256k1.signatureImportLax(buffer) - }) - }) - }) - - it('parse not bip66 signature', function () { - const signature = Buffer.from('308002204171936738571ff75ec0c56c010f339f1f6d510ba45ad936b0762b1b2162d8020220152670567fa3cc92a5ea1a6ead11741832f8aede5ca176f559e8a46bb858e3f6', 'hex') - assert.doesNotThrow(function () { - ethUtils.secp256k1.signatureImportLax(signature) - }) - }) -}) - -describe('signatureExport/signatureImport', function () { - it('signature should be a Buffer', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey) => { - const message = util.getMessage() - - const signature = util.sign(message, privateKey).signatureLowS - - const der = ethUtils.secp256k1.signatureExport(signature) - assert.deepEqual(ethUtils.secp256k1.signatureImport(der), signature) - assert.deepEqual(ethUtils.secp256k1.signatureImportLax(der), signature) - }) - }) -}) - -describe('ecdh', function () { - it('public key should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = null - ethUtils.secp256k1.ecdh(publicKey, privateKey) - }) - }) - - it('public key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed.slice(1) - ethUtils.secp256k1.ecdh(publicKey, privateKey) - }) - }) - - it('invalid public key', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - publicKey[0] = 0x00 - ethUtils.secp256k1.ecdh(publicKey, privateKey) - }) - }) - - it('secret key should be a Buffer', function () { - assert.throws(function () { - const privateKey = null - const publicKey = util.getPublicKey(util.getPrivateKey()).compressed - ethUtils.secp256k1.ecdh(publicKey, privateKey) - }) - }) - - it('secret key invalid length', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey().slice(1) - const publicKey = util.getPublicKey(util.getPrivateKey()).compressed - ethUtils.secp256k1.ecdh(publicKey, privateKey) - }) - }) - - it('secret key equal zero', function () { - assert.throws(function () { - const privateKey = util.ec.curve.zero.fromRed().toArrayLike(Buffer, 'be', 32) - const publicKey = util.getPublicKey(util.getPrivateKey()).compressed - ethUtils.secp256k1.ecdh(publicKey, privateKey) - }) - }) - - it('secret key equal N', function () { - assert.throws(function () { - const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) - const publicKey = util.getPublicKey(util.getPrivateKey()).compressed - ethUtils.secp256k1.ecdh(publicKey, privateKey) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey1, i) => { - const privateKey2 = util.getPrivateKey() - const publicKey1 = util.getPublicKey(privateKey1).compressed - const publicKey2 = util.getPublicKey(privateKey2).compressed - - const shared1 = ethUtils.secp256k1.ecdh(publicKey1, privateKey2) - const shared2 = ethUtils.secp256k1.ecdh(publicKey2, privateKey1) - assert.deepEqual(shared1, shared2) - }) - }) -}) - - -describe('ecdhUnsafe', function () { - it('public key should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = null - ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) - }) - }) - - it('public key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed.slice(1) - ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) - }) - }) - - it('invalid public key', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const publicKey = util.getPublicKey(privateKey).compressed - publicKey[0] = 0x00 - ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) - }) - }) - - it('secret key should be a Buffer', function () { - assert.throws(function () { - const privateKey = null - const publicKey = util.getPublicKey(util.getPrivateKey()).compressed - ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) - }) - }) - - it('secret key invalid length', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey().slice(1) - const publicKey = util.getPublicKey(util.getPrivateKey()).compressed - ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) - }) - }) - - it('secret key equal zero', function () { - assert.throws(function () { - const privateKey = util.ec.curve.zero.fromRed().toArrayLike(Buffer, 'be', 32) - const publicKey = util.getPublicKey(util.getPrivateKey()).compressed - ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) - }) - }) - - it('secret key equal N', function () { - assert.throws(function () { - const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) - const publicKey = util.getPublicKey(util.getPrivateKey()).compressed - ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey1, i) => { - const privateKey2 = util.getPrivateKey() - const publicKey1 = util.getPublicKey(privateKey1).compressed - const publicKey2 = util.getPublicKey(privateKey2).compressed - - const shared1 = ethUtils.secp256k1.ecdhUnsafe(publicKey1, privateKey2) - const shared2 = ethUtils.secp256k1.ecdhUnsafe(publicKey2, privateKey1) - assert.deepEqual(shared1, shared2) - }) - }) -}) - -describe('sign', function () { - it('message should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - ethUtils.secp256k1.sign(null, privateKey) - }) - }) - - it('message invalid length', function () { - assert.throws(function () { - const message = util.getMessage().slice(1) - const privateKey = util.getPrivateKey() - ethUtils.secp256k1.sign(message, privateKey) - }) - }) - - it('private key should be a Buffer', function () { - assert.throws(function () { - const message = util.getMessage() - ethUtils.secp256k1.sign(message, null) - }) - }) - - it('private key invalid length', function () { - assert.throws(function () { - const message = util.getMessage() - const privateKey = util.getPrivateKey().slice(1) - ethUtils.secp256k1.sign(message, privateKey) - }) - }) - - it('private key is invalid', function () { - assert.throws(function () { - const message = util.getMessage() - const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) - ethUtils.secp256k1.sign(message, privateKey) - }) - }) - - it('options should be an Object', function () { - assert.throws(function () { - const message = util.getMessage() - const privateKey = util.getPrivateKey() - ethUtils.secp256k1.sign(message, privateKey, null) - }) - }) - - it('options.data should be a Buffer', function () { - assert.throws(function () { - const message = util.getMessage() - const privateKey = util.getPrivateKey() - ethUtils.secp256k1.sign(message, privateKey, { data: null }) - }) - }) - - it('options.data length is invalid', function () { - assert.throws(function () { - const message = util.getMessage() - const privateKey = util.getPrivateKey() - const data = getRandomBytes(31) - ethUtils.secp256k1.sign(message, privateKey, { data: data }) - }) - }) - - it('options.noncefn should be a Function', function () { - assert.throws(function () { - const message = util.getMessage() - const privateKey = util.getPrivateKey() - ethUtils.secp256k1.sign(message, privateKey, { noncefn: null }) - }) - }) - - it('noncefn return not a Buffer', function () { - assert.throws(function () { - const message = util.getMessage() - const privateKey = util.getPrivateKey() - const noncefn = function () { return null } - ethUtils.secp256k1.sign(message, privateKey, { noncefn: noncefn }) - }) - }) - - it('noncefn return Buffer with invalid length', function () { - assert.throws(function () { - const message = util.getMessage() - const privateKey = util.getPrivateKey() - const noncefn = function () { return getRandomBytes(31) } - ethUtils.secp256k1.sign(message, privateKey, { noncefn: noncefn }) - }) - }) - - it('check options.noncefn arguments', function () { - const message = util.getMessage() - const privateKey = util.getPrivateKey() - const data = getRandomBytes(32) - const noncefn = function (message2, privateKey2, algo, data2, attempt) { - assert.deepEqual(message2, message) - assert.deepEqual(privateKey2, privateKey) - assert.deepEqual(algo, null) - assert.deepEqual(data2, data) - assert.deepEqual(attempt, 0) - return getRandomBytes(32) - } - ethUtils.secp256k1.sign(message, privateKey, { data: data, noncefn: noncefn }) - }) - -}) - -describe('verify', function () { - it('message should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey) - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.verify(null, signature, publicKey) - }) - }) - - it('message length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage().slice(1) - const signature = util.getSignature(message, privateKey) - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.verify(message, signature, publicKey) - }) - }) - - it('signature should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.verify(message, null, publicKey) - }) - }) - - it('signature length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey).slice(1) - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.verify(message, signature, publicKey) - }) - }) - - it('signature is invalid (r equal N)', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = Buffer.concat([ - util.ec.n.toArrayLike(Buffer, 'be', 32), - getRandomBytes(32) - ]) - const publicKey = util.getPublicKey(privateKey).compressed - ethUtils.secp256k1.verify(message, signature, publicKey) - }) - }) - - it('public key should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey) - ethUtils.secp256k1.verify(message, signature, null) - }) - }) - - it('public key length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey) - const publicKey = util.getPublicKey(privateKey).compressed.slice(1) - ethUtils.secp256k1.verify(message, signature, publicKey) - }) - }) - - it('public key is invalid (version is 0x01)', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey) - const publicKey = util.getPublicKey(privateKey).compressed - publicKey[0] = 0x01 - ethUtils.secp256k1.verify(message, signature, publicKey) - }) - }) - -}) - -describe('recover', function () { - it('message should be a Buffer', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey) - ethUtils.secp256k1.recover(null, signature, 0) - }) - }) - - it('message length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage().slice(1) - const signature = util.getSignature(message, privateKey) - ethUtils.secp256k1.recover(message, signature, 0) - }) - }) - - it('signature should be a Buffer', function () { - assert.throws(function () { - const message = util.getMessage() - ethUtils.secp256k1.recover(message, null, 0) - }) - }) - - it('signature length is invalid', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey).slice(1) - ethUtils.secp256k1.recover(message, signature, 0) - }) - }) - - it('signature is invalid (r equal N)', function () { - assert.throws(function () { - const message = util.getMessage() - const signature = Buffer.concat([ - util.ec.n.toArrayLike(Buffer, 'be', 32), - getRandomBytes(32) - ]) - ethUtils.secp256k1.recover(message, signature, 0) - }) - }) - - it('recovery should be a Number', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey) - ethUtils.secp256k1.recover(message, signature, null) - }) - }) - - it('recovery is invalid (equal 4)', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(privateKey, message) - ethUtils.secp256k1.recover(message, signature, 4) - }) - }) - - it('compressed should be a boolean', function () { - assert.throws(function () { - const privateKey = util.getPrivateKey() - const message = util.getMessage() - const signature = util.getSignature(message, privateKey) - ethUtils.secp256k1.recover(message, signature, 0, null) - }) - }) - - it('random tests', function () { - const privateKeys = util.getPrivateKeys(10) - - privateKeys.forEach((privateKey, i) => { - const message = util.getMessage() - const publicKey = util.getPublicKey(privateKey) - const expected = util.sign(message, privateKey) - - const sigObj = ethUtils.secp256k1.sign(message, privateKey) - assert.deepEqual(sigObj.signature, expected.signatureLowS) - assert.deepEqual(sigObj.recovery, expected.recovery) - - const isValid = ethUtils.secp256k1.verify(message, sigObj.signature, publicKey.compressed) - assert.equal(isValid, true) - - const compressed = ethUtils.secp256k1.recover(message, sigObj.signature, sigObj.recovery, true) - assert.deepEqual(compressed, publicKey.compressed) - - const uncompressed = ethUtils.secp256k1.recover(message, sigObj.signature, sigObj.recovery, false) - assert.deepEqual(uncompressed, publicKey.uncompressed) - }) - }) -}) diff --git a/test/secp256k1-adapter.js b/test/secp256k1-adapter.js new file mode 100644 index 00000000..da115646 --- /dev/null +++ b/test/secp256k1-adapter.js @@ -0,0 +1,1336 @@ +// This file is imported from secp256k1 v3 +// https://github.com/cryptocoinjs/secp256k1-node/blob/master/LICENSE + +const assert = require('assert') +const ethUtils = require('../dist/index.js') +const BN = require('bn.js') +const util = require('./util') +const getRandomBytes = require('crypto').randomBytes + +describe('privateKeyVerify', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyVerify(null) + }) + }) + + it('invalid length', function () { + assert.equal(ethUtils.secp256k1.privateKeyVerify(util.getPrivateKey().slice(1)), false) + }) + + it('zero key', function () { + const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) + assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), false) + }) + + + it('equal to N', function () { + const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), false) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + privateKeys.forEach((privateKey) => { + assert.equal(ethUtils.secp256k1.privateKeyVerify(privateKey), true) + }) + }) +}) + +describe('privateKeyExport', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyExport(null) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyExport(util.getPrivateKey().slice(1)) + }) + }) + + it('private key is invalid', function () { + assert.throws(function () { + const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyExport(privateKey) + }) + }) +}) + +describe('privateKeyImport', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyImport(null) + }) + }) + + it('invalid format', function () { + const buffers = [ + Buffer.from([0x00]), + Buffer.from([0x30, 0x7b]), + Buffer.from([0x30, 0x87]), + Buffer.from([0x30, 0x81]), + Buffer.from([0x30, 0x82, 0x00, 0xff]), + Buffer.from([0x30, 0x82, 0x00, 0x00]), + Buffer.from([0x30, 0x82, 0x00, 0x00, 0x02, 0x01, 0x01]) + ] + + buffers.forEach((buffer) => { + assert.throws(function () { + ethUtils.secp256k1.privateKeyImport(buffer) + }) + }) + }) +}) + +describe('privateKeyExport/privateKeyImport', function () { + it('export/import', function() { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const der1 = ethUtils.secp256k1.privateKeyExport(privateKey, true) + const privateKey1 = ethUtils.secp256k1.privateKeyImport(der1) + assert.deepEqual(privateKey1, privateKey) + + const der2 = ethUtils.secp256k1.privateKeyExport(privateKey, false) + const privateKey2 = ethUtils.secp256k1.privateKeyImport(der2) + assert.deepEqual(privateKey2, privateKey) + + const der3 = ethUtils.secp256k1.privateKeyExport(privateKey) + const privateKey3 = ethUtils.secp256k1.privateKeyImport(der3) + assert.deepEqual(privateKey3, privateKey) + }) + }) +}) + +describe('privateKeyNegate', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyNegate(null) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyNegate(util.getPrivateKey().slice(1)) + }) + }) + + it('private key is 0', function () { + const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) + + const expected = Buffer.alloc(32) + const result = ethUtils.secp256k1.privateKeyNegate(privateKey) + assert.deepEqual(result, expected) + }) + + it('private key equal to N', function () { + const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + + const expected = Buffer.alloc(32) + const result = ethUtils.secp256k1.privateKeyNegate(privateKey) + assert.deepEqual(result, expected) + }) + + it('private key overflow', function () { + const privateKey = util.ec.curve.n.addn(10).toArrayLike(Buffer, 'be', 32) + + const expected = util.ec.curve.n.subn(10).toArrayLike(Buffer, 'be', 32) + const result = ethUtils.secp256k1.privateKeyNegate(privateKey) + assert.deepEqual(result, expected) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + privateKeys.forEach((privateKey) => { + const expected = util.ec.curve.n.sub(new BN(privateKey)) + const result = ethUtils.secp256k1.privateKeyNegate(privateKey) + + assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) + }) + }) +}) + +describe('privateKeyModInverse', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.privateKeyModInverse(null) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + ethUtils.secp256k1.privateKeyModInverse(privateKey) + }) + }) + + it('private key is 0', function () { + assert.throws(function () { + const privateKey = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyModInverse(privateKey) + }) + }) + + it('private key equal to N', function () { + assert.throws(function () { + const privateKey = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyModInverse(privateKey) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + privateKeys.forEach((privateKey) => { + const expected = new BN(privateKey).invm(new BN(util.ec.curve.n.toArrayLike(Buffer, 'be', 32))) + const result = ethUtils.secp256k1.privateKeyModInverse(privateKey) + + assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) + }) + }) +}) + +describe('privateKeyTweakAdd', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + const tweak = util.getTweak() + ethUtils.secp256k1.privateKeyTweakAdd(null, tweak) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + const tweak = util.getTweak() + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + }) + + it('tweak should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, null) + }) + }) + + it('tweak length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.getTweak().slice(1) + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + }) + + it('tweak overflow', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + }) + + it('result is zero: (N - 1) + 1', function () { + assert.throws(function () { + const privateKey = util.ec.curve.n.sub(util.BN_ONE).toArrayLike(Buffer, 'be', 32) + const tweak = util.BN_ONE.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + const tweak = util.getTweak() + + privateKeys.forEach((privateKey) => { + const expected = new BN(privateKey).add(new BN(tweak)).mod(util.ec.curve.n) + if (expected.cmp(util.BN_ZERO) === 0) { + assert.throws(function () { + ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + }) + } else { + const result = ethUtils.secp256k1.privateKeyTweakAdd(privateKey, tweak) + assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) + } + }) + }) +}) + +describe('privateKeyTweakMul', function () { + it('private key should be a Buffer', function () { + assert.throws(function () { + const tweak = util.getPrivateKey() + ethUtils.secp256k1.privateKeyTweakMul(null, tweak) + }) + }) + + it('private key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + const tweak = util.getPrivateKey() + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + }) + + it('tweak should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.privateKeyTweakMul(privateKey, null) + }) + }) + + it('tweak length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.getTweak().slice(1) + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + }) + + it('tweak equal N', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + }) + + it('tweak is 0', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const tweak = util.BN_ZERO.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + const tweak = util.getTweak() + + privateKeys.forEach((privateKey) => { + if (new BN(tweak).cmp(util.BN_ZERO) === 0) { + assert.throws(function () { + ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + }) + } else { + const expected = new BN(privateKey).mul(new BN(tweak)).mod(util.ec.curve.n) + const result = ethUtils.secp256k1.privateKeyTweakMul(privateKey, tweak) + assert.deepEqual(result.toString('hex'), expected.toString(16, 64)) + } + }) + }) +}) + +describe('publicKeyCreate', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyCreate(null) + }) + }) + + it('invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + ethUtils.secp256k1.publicKeyCreate(privateKey) + }) + }) + + it('overflow', function () { + assert.throws(function () { + const privateKey = util.ec.curve.p.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyCreate(privateKey) + }) + }) + + it('equal zero', function () { + assert.throws(function () { + const privateKey = new BN(0).toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyCreate(privateKey) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.publicKeyCreate(privateKey, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const expected = util.getPublicKey(privateKey) + + const compressed = ethUtils.secp256k1.publicKeyCreate(privateKey, true) + assert.deepEqual(compressed, expected.compressed) + + const uncompressed = ethUtils.secp256k1.publicKeyCreate(privateKey, false) + assert.deepEqual(uncompressed, expected.uncompressed) + }) + }) +}) + +describe('publicKeyConvert', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyConvert(null) + }) + }) + + it('length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.publicKeyConvert(publicKey) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.publicKeyConvert(publicKey, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const expected = util.getPublicKey(privateKey) + + const compressed = ethUtils.secp256k1.publicKeyConvert(expected.uncompressed, true) + assert.deepEqual(compressed, expected.compressed) + + const uncompressed = ethUtils.secp256k1.publicKeyConvert(expected.uncompressed, false) + assert.deepEqual(uncompressed, expected.uncompressed) + }) + }) +}) + +describe('publicKeyVerify', function () { + it('should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyVerify(null) + }) + }) + + it('invalid length', function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('invalid first byte', function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('x overflow (first byte is 0x03)', function () { + const publicKey = Buffer.concat([ + Buffer.from([ 0x03 ]), + util.ec.curve.p.toArrayLike(Buffer, 'be', 32) + ]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('x overflow', function () { + const publicKey = Buffer.concat([ + Buffer.from([ 0x04 ]), + util.ec.curve.p.toArrayLike(Buffer, 'be', 32) + ]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('y overflow', function () { + const publicKey = Buffer.concat([ + Buffer.from([ 0x04 ]), + Buffer.alloc(32), + util.ec.curve.p.toArrayLike(Buffer, 'be', 32) + ]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('y is even, first byte is 0x07', function () { + const publicKey = Buffer.concat([ + Buffer.from([ 0x07 ]), + Buffer.alloc(32), + util.ec.curve.p.subn(1).toArrayLike(Buffer, 'be', 32) + ]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('y**2 !== x*x*x + 7', function () { + const publicKey = Buffer.concat([Buffer.from([0x04]), util.getTweak(), util.getTweak()]) + assert.equal(ethUtils.secp256k1.publicKeyVerify(publicKey), false) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const expected = util.getPublicKey(privateKey) + + assert.equal(ethUtils.secp256k1.publicKeyVerify(expected.uncompressed), true) + assert.equal(ethUtils.secp256k1.publicKeyVerify(expected.uncompressed), true) + }) + }) +}) + +describe('publicKeyTweakAdd', function () { + it('public key should be a Buffer', function () { + assert.throws(function () { + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakAdd(null, tweak) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) + }) + }) + + it('public key is invalid (version is 0x01)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) + }) + }) + + it('tweak should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, null) + }) + }) + + it('tweak length length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.getTweak().slice(1) + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) + }) + }) + + it('tweak overflow', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak) + }) + }) + + it('tweak produce infinity point', function () { + // G * 1 - G = 0 + assert.throws(function () { + const publicKey = Buffer.from(util.ec.g.encode(null, true)) + publicKey[0] = publicKey[0] ^ 0x01 // change sign of G + const tweak = new BN(1).toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, true) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const tweak = util.getTweak() + const publicPoint = util.ec.g.mul(new BN(privateKey)) + const publicKey = Buffer.from(publicPoint.encode(null, true)) + const expected = util.ec.g.mul(new BN(tweak)).add(publicPoint) + + const compressed = ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, true) + assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) + + const uncompressed = ethUtils.secp256k1.publicKeyTweakAdd(publicKey, tweak, false) + assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) + }) + }) +}) + +describe('publicKeyTweakMul', function () { + it('public key should be a Buffer', function () { + assert.throws(function () { + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakMul(null, tweak) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('public key is invalid (version is 0x01)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('tweak should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.publicKeyTweakMul(publicKey, null) + }) + }) + + it('tweak length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.getTweak().slice(1) + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('tweak is zero', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = new BN(0).toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('tweak overflow', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.ec.curve.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + const tweak = util.getTweak() + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const tweak = util.getTweak() + const publicPoint = util.ec.g.mul(new BN(privateKey)) + const publicKey = Buffer.from(publicPoint.encode(null, true)) + const expected = util.ec.g.mul(new BN(tweak)).add(publicPoint) + + if (new BN(tweak).cmp(util.BN_ZERO) === 0) { + assert.throws(function () { + ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak) + }) + } else { + const expected = publicPoint.mul(tweak) + + const compressed = ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, true) + assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) + + const uncompressed = ethUtils.secp256k1.publicKeyTweakMul(publicKey, tweak, false) + assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) + } + }) + }) +}) + +describe('publicKeyCombine', function () { + it('public keys should be an Array', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyCombine(null) + }) + }) + + it('public keys should have length greater that zero', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyCombine([]) + }) + }) + + it('public key should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.publicKeyCombine([null]) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.publicKeyCombine([publicKey]) + }) + }) + + it('public key is invalid (version is 0x01)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + ethUtils.secp256k1.publicKeyCombine([publicKey]) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.publicKeyCombine([publicKey], null) + }) + }) + + it('P + (-P) = 0', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey1 = util.getPublicKey(privateKey).compressed + const publicKey2 = Buffer.from(publicKey1) + publicKey2[0] = publicKey2[0] ^ 0x01 + ethUtils.secp256k1.publicKeyCombine([publicKey1, publicKey2], true) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const cnt = 1 + Math.floor(Math.random() * 3) // 1 <= cnt <= 3 + const privateKeys = [] + while (privateKeys.length < cnt) privateKeys.push(util.getPrivateKey()) + const publicKeys = privateKeys.map(function (privateKey) { + return util.getPublicKey(privateKey).compressed + }) + + let expected = util.ec.g.mul(new BN(privateKeys[0])) + for (let i = 1; i < privateKeys.length; ++i) { + const publicPoint = util.ec.g.mul(new BN(privateKeys[i])) + expected = expected.add(publicPoint) + } + + const compressed = ethUtils.secp256k1.publicKeyCombine(publicKeys, true) + assert.deepEqual(compressed.toString('hex'), expected.encode('hex', true)) + + const uncompressed = ethUtils.secp256k1.publicKeyCombine(publicKeys, false) + assert.deepEqual(uncompressed.toString('hex'), expected.encode('hex', false)) + }) + }) +}) + +describe('signatureNormalize', function () { + it('signature should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureNormalize(null) + }) + }) + + it('invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey).slice(1) + ethUtils.secp256k1.signatureNormalize(signature) + }) + }) + + it('parse fail (r equal N)', function () { + assert.throws(function () { + const signature = Buffer.concat([ + util.ec.curve.n.toArrayLike(Buffer, 'be', 32), + util.BN_ONE.toArrayLike(Buffer, 'be', 32) + ]) + ethUtils.secp256k1.signatureNormalize(signature) + }) + }) + + it('normalize return same signature (s equal n/2)', function () { + const signature = Buffer.concat([ + util.BN_ONE.toArrayLike(Buffer, 'be', 32), + util.ec.nh.toArrayLike(Buffer, 'be', 32) + ]) + const result = ethUtils.secp256k1.signatureNormalize(signature) + assert.deepEqual(result, signature) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const message = util.getMessage() + + const sigObj = util.sign(message, privateKey) + const result = ethUtils.secp256k1.signatureNormalize(sigObj.signature) + assert.deepEqual(result, sigObj.signatureLowS) + }) + }) +}) + +describe('signatureExport', function () { + it('signature should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureExport(null) + }) + }) + + it('invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey).slice(1) + ethUtils.secp256k1.signatureExport(signature) + }) + }) + + it('parse fail (r equal N)', function () { + assert.throws(function () { + const signature = Buffer.concat([ + util.ec.n.toArrayLike(Buffer, 'be', 32), + util.BN_ONE.toArrayLike(Buffer, 'be', 32) + ]) + ethUtils.secp256k1.signatureExport(signature) + }) + }) + +}) + +describe('signatureImport', function () { + it('signature should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureImport(null) + }) + }) + + it('parse fail', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureImport(Buffer.alloc(1)) + }) + }) + + it('parse not bip66 signature', function () { + const signature = Buffer.from('308002204171936738571ff75ec0c56c010f339f1f6d510ba45ad936b0762b1b2162d8020220152670567fa3cc92a5ea1a6ead11741832f8aede5ca176f559e8a46bb858e3f6', 'hex') + assert.throws(function () { + ethUtils.secp256k1.signatureImport(signature) + }) + }) + +}) + +describe('signatureImportLax', function () { + it('signature should be a Buffer', function () { + assert.throws(function () { + ethUtils.secp256k1.signatureImportLax(null) + }) + }) + + it('parse fail', function () { + const buffers = [ + Buffer.alloc(0), + Buffer.alloc(1), + Buffer.from([0x30, 0x7b]), + Buffer.from([0x30, 0x87]), + Buffer.from([0x30, 0x80, 0x02, 0x80]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01]), + Buffer.from([0x30, 0x82, 0x00, 0x00, 0x02, 0x01, 0x01]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01, 0x00, 0x02, 0x81]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x81, 0x01, 0x00, 0x02, 0x81, 0x01]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x21, 0x01, 0x00, 0x02, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02]), + Buffer.from([0x30, 0x81, 0x00, 0x02, 0x05, 0x01, 0x00, 0x02, 0x21, 0x02, 0x02, 0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + ] + + buffers.forEach((buffer) => { + assert.throws(function () { + ethUtils.secp256k1.signatureImportLax(buffer) + }) + }) + }) + + it('parse not bip66 signature', function () { + const signature = Buffer.from('308002204171936738571ff75ec0c56c010f339f1f6d510ba45ad936b0762b1b2162d8020220152670567fa3cc92a5ea1a6ead11741832f8aede5ca176f559e8a46bb858e3f6', 'hex') + assert.doesNotThrow(function () { + ethUtils.secp256k1.signatureImportLax(signature) + }) + }) +}) + +describe('signatureExport/signatureImport', function () { + it('signature should be a Buffer', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey) => { + const message = util.getMessage() + + const signature = util.sign(message, privateKey).signatureLowS + + const der = ethUtils.secp256k1.signatureExport(signature) + assert.deepEqual(ethUtils.secp256k1.signatureImport(der), signature) + assert.deepEqual(ethUtils.secp256k1.signatureImportLax(der), signature) + }) + }) +}) + +describe('ecdh', function () { + it('public key should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = null + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('invalid public key', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x00 + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('secret key should be a Buffer', function () { + assert.throws(function () { + const privateKey = null + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('secret key invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('secret key equal zero', function () { + assert.throws(function () { + const privateKey = util.ec.curve.zero.fromRed().toArrayLike(Buffer, 'be', 32) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('secret key equal N', function () { + assert.throws(function () { + const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdh(publicKey, privateKey) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey1, i) => { + const privateKey2 = util.getPrivateKey() + const publicKey1 = util.getPublicKey(privateKey1).compressed + const publicKey2 = util.getPublicKey(privateKey2).compressed + + const shared1 = ethUtils.secp256k1.ecdh(publicKey1, privateKey2) + const shared2 = ethUtils.secp256k1.ecdh(publicKey2, privateKey1) + assert.deepEqual(shared1, shared2) + }) + }) +}) + + +describe('ecdhUnsafe', function () { + it('public key should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = null + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('invalid public key', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x00 + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('secret key should be a Buffer', function () { + assert.throws(function () { + const privateKey = null + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('secret key invalid length', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey().slice(1) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('secret key equal zero', function () { + assert.throws(function () { + const privateKey = util.ec.curve.zero.fromRed().toArrayLike(Buffer, 'be', 32) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('secret key equal N', function () { + assert.throws(function () { + const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) + const publicKey = util.getPublicKey(util.getPrivateKey()).compressed + ethUtils.secp256k1.ecdhUnsafe(publicKey, privateKey) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey1, i) => { + const privateKey2 = util.getPrivateKey() + const publicKey1 = util.getPublicKey(privateKey1).compressed + const publicKey2 = util.getPublicKey(privateKey2).compressed + + const shared1 = ethUtils.secp256k1.ecdhUnsafe(publicKey1, privateKey2) + const shared2 = ethUtils.secp256k1.ecdhUnsafe(publicKey2, privateKey1) + assert.deepEqual(shared1, shared2) + }) + }) +}) + +describe('sign', function () { + it('message should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(null, privateKey) + }) + }) + + it('message invalid length', function () { + assert.throws(function () { + const message = util.getMessage().slice(1) + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(message, privateKey) + }) + }) + + it('private key should be a Buffer', function () { + assert.throws(function () { + const message = util.getMessage() + ethUtils.secp256k1.sign(message, null) + }) + }) + + it('private key invalid length', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey().slice(1) + ethUtils.secp256k1.sign(message, privateKey) + }) + }) + + it('private key is invalid', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.ec.n.toArrayLike(Buffer, 'be', 32) + ethUtils.secp256k1.sign(message, privateKey) + }) + }) + + it('options should be an Object', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(message, privateKey, null) + }) + }) + + it('options.data should be a Buffer', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(message, privateKey, { data: null }) + }) + }) + + it('options.data length is invalid', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + const data = getRandomBytes(31) + ethUtils.secp256k1.sign(message, privateKey, { data: data }) + }) + }) + + it('options.noncefn should be a Function', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + ethUtils.secp256k1.sign(message, privateKey, { noncefn: null }) + }) + }) + + it('noncefn return not a Buffer', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + const noncefn = function () { return null } + ethUtils.secp256k1.sign(message, privateKey, { noncefn: noncefn }) + }) + }) + + it('noncefn return Buffer with invalid length', function () { + assert.throws(function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + const noncefn = function () { return getRandomBytes(31) } + ethUtils.secp256k1.sign(message, privateKey, { noncefn: noncefn }) + }) + }) + + it('check options.noncefn arguments', function () { + const message = util.getMessage() + const privateKey = util.getPrivateKey() + const data = getRandomBytes(32) + const noncefn = function (message2, privateKey2, algo, data2, attempt) { + assert.deepEqual(message2, message) + assert.deepEqual(privateKey2, privateKey) + assert.deepEqual(algo, null) + assert.deepEqual(data2, data) + assert.deepEqual(attempt, 0) + return getRandomBytes(32) + } + ethUtils.secp256k1.sign(message, privateKey, { data: data, noncefn: noncefn }) + }) + +}) + +describe('verify', function () { + it('message should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(null, signature, publicKey) + }) + }) + + it('message length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage().slice(1) + const signature = util.getSignature(message, privateKey) + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + + it('signature should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(message, null, publicKey) + }) + }) + + it('signature length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey).slice(1) + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + + it('signature is invalid (r equal N)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = Buffer.concat([ + util.ec.n.toArrayLike(Buffer, 'be', 32), + getRandomBytes(32) + ]) + const publicKey = util.getPublicKey(privateKey).compressed + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + + it('public key should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.verify(message, signature, null) + }) + }) + + it('public key length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + const publicKey = util.getPublicKey(privateKey).compressed.slice(1) + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + + it('public key is invalid (version is 0x01)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + const publicKey = util.getPublicKey(privateKey).compressed + publicKey[0] = 0x01 + ethUtils.secp256k1.verify(message, signature, publicKey) + }) + }) + +}) + +describe('recover', function () { + it('message should be a Buffer', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.recover(null, signature, 0) + }) + }) + + it('message length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage().slice(1) + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.recover(message, signature, 0) + }) + }) + + it('signature should be a Buffer', function () { + assert.throws(function () { + const message = util.getMessage() + ethUtils.secp256k1.recover(message, null, 0) + }) + }) + + it('signature length is invalid', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey).slice(1) + ethUtils.secp256k1.recover(message, signature, 0) + }) + }) + + it('signature is invalid (r equal N)', function () { + assert.throws(function () { + const message = util.getMessage() + const signature = Buffer.concat([ + util.ec.n.toArrayLike(Buffer, 'be', 32), + getRandomBytes(32) + ]) + ethUtils.secp256k1.recover(message, signature, 0) + }) + }) + + it('recovery should be a Number', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.recover(message, signature, null) + }) + }) + + it('recovery is invalid (equal 4)', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(privateKey, message) + ethUtils.secp256k1.recover(message, signature, 4) + }) + }) + + it('compressed should be a boolean', function () { + assert.throws(function () { + const privateKey = util.getPrivateKey() + const message = util.getMessage() + const signature = util.getSignature(message, privateKey) + ethUtils.secp256k1.recover(message, signature, 0, null) + }) + }) + + it('random tests', function () { + const privateKeys = util.getPrivateKeys(10) + + privateKeys.forEach((privateKey, i) => { + const message = util.getMessage() + const publicKey = util.getPublicKey(privateKey) + const expected = util.sign(message, privateKey) + + const sigObj = ethUtils.secp256k1.sign(message, privateKey) + assert.deepEqual(sigObj.signature, expected.signatureLowS) + assert.deepEqual(sigObj.recovery, expected.recovery) + + const isValid = ethUtils.secp256k1.verify(message, sigObj.signature, publicKey.compressed) + assert.equal(isValid, true) + + const compressed = ethUtils.secp256k1.recover(message, sigObj.signature, sigObj.recovery, true) + assert.deepEqual(compressed, publicKey.compressed) + + const uncompressed = ethUtils.secp256k1.recover(message, sigObj.signature, sigObj.recovery, false) + assert.deepEqual(uncompressed, publicKey.uncompressed) + }) + }) +}) \ No newline at end of file From 3876bde004ff6b53cfdc470267e910f34e973c0a Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Tue, 14 Jul 2020 12:07:36 +0200 Subject: [PATCH 14/15] Require secp256k1 from ethereum-cryptography. --- src/secp256k1v3-adapter.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/secp256k1v3-adapter.ts b/src/secp256k1v3-adapter.ts index 60e34dab..7bcb05b3 100644 --- a/src/secp256k1v3-adapter.ts +++ b/src/secp256k1v3-adapter.ts @@ -1,4 +1,4 @@ -import * as secp256k1 from 'ethereum-cryptography/secp256k1' +const secp256k1 = require('ethereum-cryptography/secp256k1') const secp256k1v3 = require('./secp256k1v3-lib/index') const der = require('./secp256k1v3-lib/der') @@ -13,6 +13,17 @@ export interface SignOptions { ) => Buffer } +export interface SignOptionsV4 { + data?: Uint8Array + noncefn?: ( + message: Uint8Array, + privateKey: Uint8Array, + algo: Uint8Array | null, + data: Uint8Array | null, + attempt: number, + ) => Uint8Array +} + export const privateKeyVerify = function(privateKey: Buffer): boolean { if (privateKey.length !== 32) { return false @@ -141,7 +152,7 @@ export const sign = function( throw new TypeError('options should be an Object') } - let signOptions: secp256k1.SignOptions | undefined = undefined + let signOptions: SignOptionsV4 | undefined = undefined if (options) { signOptions = {} From 71ccac594d7220dbf99eb61c23141fb52f83b8d1 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Tue, 14 Jul 2020 20:03:17 +0200 Subject: [PATCH 15/15] Add comments to secp256k1 wrapper. --- src/secp256k1v3-adapter.ts | 159 +++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/secp256k1v3-adapter.ts b/src/secp256k1v3-adapter.ts index 7bcb05b3..9c60ef06 100644 --- a/src/secp256k1v3-adapter.ts +++ b/src/secp256k1v3-adapter.ts @@ -24,7 +24,14 @@ export interface SignOptionsV4 { ) => Uint8Array } +/** + * Verify an ECDSA privateKey + * @method privateKeyVerify + * @param {Buffer} privateKey + * @return {boolean} + */ export const privateKeyVerify = function(privateKey: Buffer): boolean { + // secp256k1 v4 version throws when privateKey length is not 32 if (privateKey.length !== 32) { return false } @@ -32,7 +39,15 @@ export const privateKeyVerify = function(privateKey: Buffer): boolean { return secp256k1.privateKeyVerify(Uint8Array.from(privateKey)) } +/** + * Export a privateKey in DER format + * @method privateKeyExport + * @param {Buffer} privateKey + * @param {boolean} compressed + * @return {boolean} + */ export const privateKeyExport = function(privateKey: Buffer, compressed?: boolean): Buffer { + // secp256k1 v4 version throws when privateKey length is not 32 if (privateKey.length !== 32) { throw new RangeError('private key length is invalid') } @@ -42,7 +57,15 @@ export const privateKeyExport = function(privateKey: Buffer, compressed?: boolea return der.privateKeyExport(privateKey, publicKey, compressed) } +/** + * Import a privateKey in DER format + * @method privateKeyImport + * @param {Buffer} privateKey + * @return {Buffer} + */ export const privateKeyImport = function(privateKey: Buffer): Buffer { + // privateKeyImport method is not part of secp256k1 v4 package + // this implementation is based on v3 privateKey = der.privateKeyImport(privateKey) if (privateKey !== null && privateKey.length === 32 && privateKeyVerify(privateKey)) { return privateKey @@ -51,10 +74,22 @@ export const privateKeyImport = function(privateKey: Buffer): Buffer { throw new Error("couldn't import from DER format") } +/** + * Negate a privateKey by subtracting it from the order of the curve's base point + * @method privateKeyNegate + * @param {Buffer} privateKey + * @return {Buffer} + */ export const privateKeyNegate = function(privateKey: Buffer): Buffer { return Buffer.from(secp256k1.privateKeyNegate(Uint8Array.from(privateKey))) } +/** + * Compute the inverse of a privateKey (modulo the order of the curve's base point). + * @method privateKeyModInverse + * @param {Buffer} privateKey + * @return {Buffer} + */ export const privateKeyModInverse = function(privateKey: Buffer): Buffer { if (privateKey.length !== 32) { throw new Error('private key length is invalid') @@ -63,25 +98,60 @@ export const privateKeyModInverse = function(privateKey: Buffer): Buffer { return Buffer.from(secp256k1v3.privateKeyModInverse(Uint8Array.from(privateKey))) } +/** + * Tweak a privateKey by adding tweak to it. + * @method privateKeyTweakAdd + * @param {Buffer} privateKey + * @param {Buffer} tweak + * @return {Buffer} + */ export const privateKeyTweakAdd = function(privateKey: Buffer, tweak: Buffer): Buffer { return Buffer.from(secp256k1.privateKeyTweakAdd(Uint8Array.from(privateKey), tweak)) } +/** + * Tweak a privateKey by multiplying it by a tweak. + * @method privateKeyTweakMul + * @param {Buffer} privateKey + * @param {Buffer} tweak + * @return {Buffer} + */ export const privateKeyTweakMul = function(privateKey: Buffer, tweak: Buffer): Buffer { return Buffer.from( secp256k1.privateKeyTweakMul(Uint8Array.from(privateKey), Uint8Array.from(tweak)), ) } +/** + * Compute the public key for a privateKey. + * @method publicKeyCreate + * @param {Buffer} privateKey + * @param {boolean} compressed + * @return {Buffer} + */ export const publicKeyCreate = function(privateKey: Buffer, compressed?: boolean): Buffer { return Buffer.from(secp256k1.publicKeyCreate(Uint8Array.from(privateKey), compressed)) } +/** + * Convert a publicKey to compressed or uncompressed form. + * @method publicKeyConvert + * @param {Buffer} publicKey + * @param {boolean} compressed + * @return {Buffer} + */ export const publicKeyConvert = function(publicKey: Buffer, compressed?: boolean): Buffer { return Buffer.from(secp256k1.publicKeyConvert(Uint8Array.from(publicKey), compressed)) } +/** + * Verify an ECDSA publicKey. + * @method publicKeyVerify + * @param {Buffer} publicKey + * @return {boolean} + */ export const publicKeyVerify = function(publicKey: Buffer): boolean { + // secp256k1 v4 version throws when publicKey length is not 33 or 65 if (publicKey.length !== 33 && publicKey.length !== 65) { return false } @@ -89,6 +159,14 @@ export const publicKeyVerify = function(publicKey: Buffer): boolean { return secp256k1.publicKeyVerify(Uint8Array.from(publicKey)) } +/** + * Tweak a publicKey by adding tweak times the generator to it. + * @method publicKeyTweakAdd + * @param {Buffer} publicKey + * @param {Buffer} tweak + * @param {boolean} compressed + * @return {Buffer} + */ export const publicKeyTweakAdd = function( publicKey: Buffer, tweak: Buffer, @@ -99,6 +177,14 @@ export const publicKeyTweakAdd = function( ) } +/** + * Tweak a publicKey by multiplying it by a tweak value + * @method publicKeyTweakMul + * @param {Buffer} publicKey + * @param {Buffer} tweak + * @param {boolean} compressed + * @return {Buffer} + */ export const publicKeyTweakMul = function( publicKey: Buffer, tweak: Buffer, @@ -109,6 +195,13 @@ export const publicKeyTweakMul = function( ) } +/** + * Add a given publicKeys together. + * @method publicKeyCombine + * @param {Array} publicKeys + * @param {boolean} compressed + * @return {Buffer} + */ export const publicKeyCombine = function(publicKeys: Buffer[], compressed?: boolean): Buffer { const keys: Uint8Array[] = [] publicKeys.forEach((publicKey: Buffer) => { @@ -118,19 +211,46 @@ export const publicKeyCombine = function(publicKeys: Buffer[], compressed?: bool return Buffer.from(secp256k1.publicKeyCombine(keys, compressed)) } +/** + * Convert a signature to a normalized lower-S form. + * @method signatureNormalize + * @param {Buffer} signature + * @return {Buffer} + */ export const signatureNormalize = function(signature: Buffer): Buffer { return Buffer.from(secp256k1.signatureNormalize(Uint8Array.from(signature))) } +/** + * Serialize an ECDSA signature in DER format. + * @method signatureExport + * @param {Buffer} signature + * @return {Buffer} + */ export const signatureExport = function(signature: Buffer): Buffer { return Buffer.from(secp256k1.signatureExport(Uint8Array.from(signature))) } +/** + * Parse a DER ECDSA signature (follow by [BIP66](https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki)). + * @method signatureImport + * @param {Buffer} signature + * @return {Buffer} + */ export const signatureImport = function(signature: Buffer): Buffer { return Buffer.from(secp256k1.signatureImport(Uint8Array.from(signature))) } +/** + * Parse a DER ECDSA signature (not follow by [BIP66](https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki)). + * @method signatureImportLax + * @param {Buffer} signature + * @return {Buffer} + */ export const signatureImportLax = function(signature: Buffer): Buffer { + // signatureImportLax method is not part of secp256k1 v4 package + // this implementation is based on v3 + // ensure that signature is greater than 0 if (signature.length === 0) { throw new RangeError('signature length is invalid') } @@ -143,6 +263,14 @@ export const signatureImportLax = function(signature: Buffer): Buffer { return secp256k1v3.signatureImport(sigObj) } +/** + * Create an ECDSA signature. Always return low-S signature. + * @method sign + * @param {Buffer} message + * @param {Buffer} privateKey + * @param {Object} options + * @return {Buffer} + */ export const sign = function( message: Buffer, privateKey: Buffer, @@ -158,6 +286,7 @@ export const sign = function( signOptions = {} if (options.data === null) { + // validate option.data length throw new TypeError('options.data should be a Buffer') } @@ -174,6 +303,7 @@ export const sign = function( } if (options.noncefn) { + // convert option.noncefn function signature signOptions.noncefn = ( message: Uint8Array, privateKey: Uint8Array, @@ -213,10 +343,27 @@ export const sign = function( } } +/** + * Verify an ECDSA signature. + * @method verify + * @param {Buffer} message + * @param {Buffer} signature + * @param {Buffer} publicKey + * @return {boolean} + */ export const verify = function(message: Buffer, signature: Buffer, publicKey: Buffer): boolean { return secp256k1.ecdsaVerify(Uint8Array.from(signature), Uint8Array.from(message), publicKey) } +/** + * Recover an ECDSA public key from a signature. + * @method recover + * @param {Buffer} message + * @param {Buffer} signature + * @param {Number} recid + * @param {boolean} compressed + * @return {Buffer} + */ export const recover = function( message: Buffer, signature: Buffer, @@ -228,7 +375,15 @@ export const recover = function( ) } +/** + * Compute an EC Diffie-Hellman secret and applied sha256 to compressed public key. + * @method ecdh + * @param {Buffer} publicKey + * @param {Buffer} privateKey + * @return {Buffer} + */ export const ecdh = function(publicKey: Buffer, privateKey: Buffer): Buffer { + // note: secp256k1 v3 doesn't allow optional parameter return Buffer.from(secp256k1.ecdh(Uint8Array.from(publicKey), Uint8Array.from(privateKey), {})) } @@ -237,10 +392,14 @@ export const ecdhUnsafe = function( privateKey: Buffer, compressed?: boolean, ): Buffer { + // ecdhUnsafe method is not part of secp256k1 v4 package + // this implementation is based on v3 + // ensure valid publicKey length if (publicKey.length !== 33 && publicKey.length !== 65) { throw new RangeError('public key length is invalid') } + // ensure valid privateKey length if (privateKey.length !== 32) { throw new RangeError('private key length is invalid') }