diff --git a/package.json b/package.json index 7ec82335..3c0d075c 100644 --- a/package.json +++ b/package.json @@ -91,10 +91,10 @@ "@types/bn.js": "^4.11.3", "bn.js": "^4.11.0", "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "elliptic": "^6.5.2", "ethjs-util": "0.1.6", - "keccak": "^2.0.0", - "rlp": "^2.2.3", - "secp256k1": "^3.0.1" + "rlp": "^2.2.3" }, "devDependencies": { "@ethereumjs/config-prettier": "^1.1.0", diff --git a/src/account.ts b/src/account.ts index 79e8e0a5..5c9e0be7 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,6 +1,6 @@ const assert = require('assert') const ethjsUtil = require('ethjs-util') -const secp256k1 = require('secp256k1') +const secp256k1 = require('./secp256k1v3-adapter') import BN = require('bn.js') import { toBuffer, addHexPrefix, zeros, bufferToHex, unpad } from './bytes' import { keccak, keccak256, rlphash } from './hash' 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/index.ts b/src/index.ts index c44af02e..a8cbc30d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -const secp256k1 = require('secp256k1') +const secp256k1 = require('./secp256k1v3-adapter') const ethjsUtil = require('ethjs-util') import BN = require('bn.js') import rlp = require('rlp') diff --git a/src/secp256k1v3-adapter.ts b/src/secp256k1v3-adapter.ts new file mode 100644 index 00000000..9c60ef06 --- /dev/null +++ b/src/secp256k1v3-adapter.ts @@ -0,0 +1,410 @@ +const secp256k1 = require('ethereum-cryptography/secp256k1') +const secp256k1v3 = require('./secp256k1v3-lib/index') +const der = require('./secp256k1v3-lib/der') + +export interface SignOptions { + data?: Buffer + noncefn?: ( + message: Buffer, + privateKey: Buffer, + algo: Buffer | null, + data: Buffer | null, + attempt: number, + ) => Buffer +} + +export interface SignOptionsV4 { + data?: Uint8Array + noncefn?: ( + message: Uint8Array, + privateKey: Uint8Array, + algo: Uint8Array | null, + data: Uint8Array | null, + attempt: number, + ) => 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 + } + + 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') + } + + const publicKey = secp256k1v3.privateKeyExport(privateKey, compressed) + + 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 + } + + 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') + } + + 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 + } + + 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, + compressed?: boolean, +): Buffer { + return Buffer.from( + secp256k1.publicKeyTweakAdd(Uint8Array.from(publicKey), Uint8Array.from(tweak), compressed), + ) +} + +/** + * 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, + compressed?: boolean, +): Buffer { + return Buffer.from( + secp256k1.publicKeyTweakMul(Uint8Array.from(publicKey), Uint8Array.from(tweak), compressed), + ) +} + +/** + * 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) => { + keys.push(Uint8Array.from(publicKey)) + }) + + 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') + } + + const sigObj = der.signatureImportLax(signature) + if (sigObj === null) { + throw new Error("couldn't parse DER signature") + } + + 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, + options?: SignOptions, +): { signature: Buffer; recovery: number } { + if (options === null) { + throw new TypeError('options should be an Object') + } + + let signOptions: SignOptionsV4 | undefined = undefined + + if (options) { + signOptions = {} + + if (options.data === null) { + // validate option.data length + 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) { + // convert option.noncefn function signature + signOptions.noncefn = ( + message: Uint8Array, + privateKey: Uint8Array, + algo: Uint8Array | null, + data: Uint8Array | null, + attempt: number, + ) => { + const bufferAlgo: Buffer | null = algo != null ? Buffer.from(algo) : null + const bufferData: Buffer | null = data != null ? Buffer.from(data) : null + + let buffer: Buffer = Buffer.from('') + + 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), + signOptions, + ) + + return { + signature: Buffer.from(sig.signature), + recovery: sig.recid, + } +} + +/** + * 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, + recid: number, + compressed?: boolean, +): Buffer { + return Buffer.from( + secp256k1.ecdsaRecover(Uint8Array.from(signature), recid, Uint8Array.from(message), compressed), + ) +} + +/** + * 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), {})) +} + +export const ecdhUnsafe = function( + publicKey: Buffer, + 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') + } + + return Buffer.from( + secp256k1v3.ecdhUnsafe(Uint8Array.from(publicKey), Uint8Array.from(privateKey), compressed), + ) +} diff --git a/src/secp256k1v3-lib/der.ts b/src/secp256k1v3-lib/der.ts new file mode 100644 index 00000000..946c05ce --- /dev/null +++ b/src/secp256k1v3-lib/der.ts @@ -0,0 +1,653 @@ +// 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 + 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/secp256k1v3-lib/index.ts b/src/secp256k1v3-lib/index.ts new file mode 100644 index 00000000..210adc3c --- /dev/null +++ b/src/secp256k1v3-lib/index.ts @@ -0,0 +1,79 @@ +// 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 + +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("couldn't export to DER format") + } + + 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).toArrayLike(Buffer, 'be', 32) +} + +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.toArrayLike(Buffer, 'be', 32), s.toArrayLike(Buffer, 'be', 32)]) +} + +exports.ecdhUnsafe = function( + publicKey: Buffer, + privateKey: Buffer, + compressed: boolean = true, +): Buffer { + const point = ec.keyFromPublic(publicKey) + + 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 + + if (compressed) { + publicKey = Buffer.alloc(33) + publicKey[0] = y.isOdd() ? 0x03 : 0x02 + x.toArrayLike(Buffer, 'be', 32).copy(publicKey, 1) + } else { + publicKey = Buffer.alloc(65) + publicKey[0] = 0x04 + x.toArrayLike(Buffer, 'be', 32).copy(publicKey, 1) + y.toArrayLike(Buffer, 'be', 32).copy(publicKey, 33) + } + + return publicKey +} diff --git a/src/signature.ts b/src/signature.ts index 0149b6ba..5489360a 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -1,4 +1,4 @@ -const secp256k1 = require('secp256k1') +const secp256k1 = require('./secp256k1v3-adapter') import BN = require('bn.js') import { toBuffer, setLength, setLengthLeft, bufferToHex } from './bytes' import { keccak } from './hash' diff --git a/test/index.js b/test/index.js index 9ae9b96f..ee17179e 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 () { @@ -618,6 +644,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) 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 diff --git a/test/util.js b/test/util.js new file mode 100644 index 00000000..777e3b71 --- /dev/null +++ b/test/util.js @@ -0,0 +1,92 @@ +// 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') +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, +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy