Last active
December 6, 2018 18:44
-
-
Save karzak/d00b765a7648a7942cc9b82b8836a1de to your computer and use it in GitHub Desktop.
ECDSA Sign and Recover Benchmarks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as Benchmarkify from "benchmarkify"; | |
import Web3 = require("web3"); | |
import { | |
instantiateSecp256k1, | |
Secp256k1, | |
RecoverableSignature | |
} from "bitcoin-ts"; | |
import EthCrypto from 'eth-crypto'; | |
import { keccak256 } from "js-sha3"; | |
import secp256k1 = require("secp256k1"); | |
interface Secp256k1RecoverableSignature { | |
signature: Buffer; | |
recovery: number; | |
} | |
const TARGET_SIGNATURE = | |
"4df9cd2743af4edcae02ee70dfa95cc3d61bcfad788b7872a82507f2403d7d5748bcdaa0c728f7f99a91a4999b9afbf7f2974649d987eb80c7c737afee192290"; | |
const TARGET_ADDRESS = "c9039ceEc50B2ae3907756cB0CC5419B3c8696d9".toLowerCase(); | |
const web3 = new Web3("wss://kovan.infura.io/ws"); | |
const web3PrivKey = | |
"0X" + "FE2DD43AAB8D2414AAC2DADD6A9B513E0B4C02A7328B45D09C73031140EEAADD"; | |
const address = web3.eth.accounts.wallet.add(web3PrivKey).address; | |
const message = "some data"; | |
const prefix = "\x19Ethereum Signed Message:\n"; | |
const privKey = new Buffer( | |
"FE2DD43AAB8D2414AAC2DADD6A9B513E0B4C02A7328B45D09C73031140EEAADD", | |
"hex" | |
); | |
const messageBuffer = new Buffer( | |
keccak256(prefix + message.length + message), | |
"hex" | |
); | |
const messageBufferString = messageBuffer.toString('hex') | |
let scope = { | |
secp256k1bts: null as Secp256k1, | |
web3SignedMessage: null as string, | |
web3SignedMessageAlternate: null as string, | |
secp256k1Signature: null as Secp256k1RecoverableSignature, | |
secp256k1btsSignature: null as RecoverableSignature, | |
ethCryptoSignedMessage: null as string | |
}; | |
const CONFIG = { | |
// duration of each cycle | |
time: 1000, | |
// run suite functions only once, useful if you want to console.log | |
once: process.argv.includes("--once") | |
}; | |
async function setup() { | |
scope.secp256k1bts = await instantiateSecp256k1(); | |
scope.secp256k1btsSignature = scope.secp256k1bts.signMessageHashRecoverableCompact( | |
privKey, | |
messageBuffer | |
); | |
scope.secp256k1Signature = secp256k1.sign(messageBuffer, privKey); | |
scope.web3SignedMessage = await web3.eth.sign(message, address); | |
scope.web3SignedMessageAlternate = (await web3.eth.accounts.sign(message, web3PrivKey)).signature | |
//@ts-ignore | |
scope.ethCryptoSignedMessage = EthCrypto.sign(web3PrivKey.slice(2), messageBuffer.toString('hex')) | |
} | |
async function verify() { | |
console.assert( | |
Buffer.from(scope.secp256k1btsSignature.signature).toString("hex") === | |
TARGET_SIGNATURE, | |
`Invalid signature for bitcoin-ts: ${Buffer.from( | |
scope.secp256k1btsSignature.signature | |
).toString("hex")}` | |
); | |
console.assert( | |
scope.secp256k1Signature.signature.toString("hex") === TARGET_SIGNATURE, | |
`Invalid signature for secp256k1: ${scope.secp256k1Signature.signature.toString( | |
"hex" | |
)}` | |
); | |
// Remove 0x prefix and recovery value suffix for web3 | |
console.assert( | |
scope.web3SignedMessage.slice(2, -2) === TARGET_SIGNATURE, | |
`Invalid signature for web3.eth.sign: ${scope.web3SignedMessage.slice(2, -2)}` | |
); | |
console.assert( | |
scope.web3SignedMessageAlternate.slice(2, -2) === TARGET_SIGNATURE, | |
`Invalid signature for web3.eth.accounts.sign: ${scope.web3SignedMessageAlternate.slice(2, -2)}` | |
); | |
console.assert( | |
scope.ethCryptoSignedMessage.slice(2, -2) === TARGET_SIGNATURE, | |
`Invalid signature for ethCrypto.sign: ${scope.ethCryptoSignedMessage.slice(2, -2)}` | |
); | |
const bitcoinTsRecoveryAddress = keccak256( | |
scope.secp256k1bts | |
.recoverPublicKeyUncompressed( | |
scope.secp256k1btsSignature.signature, | |
scope.secp256k1btsSignature.recoveryId, | |
messageBuffer | |
) | |
.slice(1) | |
).slice(64 - 40); | |
const secp256k1RecoveryAddress = keccak256( | |
secp256k1 | |
.recover( | |
messageBuffer, | |
scope.secp256k1Signature.signature, | |
scope.secp256k1Signature.recovery, | |
false | |
) | |
.slice(1) | |
).slice(64 - 40); | |
const ethCryptoRecoveryAddress = EthCrypto.recover( | |
scope.ethCryptoSignedMessage, messageBuffer.toString('hex') | |
) | |
.slice(2).toLowerCase() | |
const web3RecoveryAddress = web3.eth.accounts | |
.recover(message, scope.web3SignedMessage) | |
.slice(2) | |
.toLowerCase(); | |
console.assert( | |
bitcoinTsRecoveryAddress === TARGET_ADDRESS, | |
`Invalid recovery address for bitcoin-ts ${bitcoinTsRecoveryAddress}` | |
); | |
console.assert( | |
ethCryptoRecoveryAddress === TARGET_ADDRESS, | |
`Invalid recovery address for ethCrypto ${ethCryptoRecoveryAddress}` | |
); | |
console.assert( | |
secp256k1RecoveryAddress === TARGET_ADDRESS, | |
`Invalid recovery address for secp256k1 ${secp256k1RecoveryAddress}` | |
); | |
console.assert( | |
web3RecoveryAddress === TARGET_ADDRESS, | |
`Invalid recovery address for web3 ${web3RecoveryAddress}` | |
); | |
} | |
const benchmark = new Benchmarkify() | |
const signSuite = benchmark.createSuite( | |
"ECDSA Sign", | |
CONFIG.once ? { time: 1, cycles: 1, minSamples: 1 } : { time: CONFIG.time } | |
); | |
signSuite.add("secp256k1.sign#test", function() { | |
secp256k1.sign(messageBuffer, privKey); | |
}); | |
signSuite.add("ethCrypto.sign#test", function() { | |
EthCrypto.sign(web3PrivKey.slice(2), messageBuffer.toString('hex')); | |
}); | |
signSuite.add("bitcoin-ts.sign#test", async function() { | |
scope.secp256k1bts.signMessageHashRecoverableCompact(privKey, messageBuffer); | |
}); | |
signSuite.add("web3.eth.sign#test", async function(next) { | |
await web3.eth.sign(message, address); | |
next(); | |
}); | |
signSuite.add("web3.eth.accounts.sign#test", async function(next) { | |
await web3.eth.accounts.sign(message, web3PrivKey); | |
next(); | |
}); | |
const recoverSuite = benchmark.createSuite('ECDSA Recover', | |
CONFIG.once ? { time: 1, cycles: 1, minSamples: 1 } : { time: CONFIG.time } | |
) | |
recoverSuite.add('secp256k1.recoverPubkey#test', function() { | |
secp256k1.recover( | |
messageBuffer, | |
scope.secp256k1Signature.signature, | |
scope.secp256k1Signature.recovery, | |
false) | |
}) | |
recoverSuite.add('secp256k1.recoverAddress#test', function() { | |
keccak256( | |
secp256k1.recover( | |
messageBuffer, | |
scope.secp256k1Signature.signature, | |
scope.secp256k1Signature.recovery, | |
false) | |
.slice(1) | |
) | |
.slice(64 - 40) | |
}) | |
recoverSuite.add('ethCrypto.recoverAddress#test', function() { | |
EthCrypto.recover(scope.ethCryptoSignedMessage, messageBufferString) | |
}) | |
recoverSuite.add('bitcoin-ts.recoverPubkey#test', function() { | |
scope.secp256k1bts.recoverPublicKeyUncompressed( | |
scope.secp256k1btsSignature.signature, | |
scope.secp256k1btsSignature.recoveryId, | |
messageBuffer) | |
}) | |
recoverSuite.add('bitcoin-ts.recoverAddress#test', function() { | |
keccak256( | |
scope.secp256k1bts.recoverPublicKeyUncompressed( | |
scope.secp256k1btsSignature.signature, | |
scope.secp256k1btsSignature.recoveryId, | |
messageBuffer) | |
.slice(1) | |
) | |
.slice(64 - 40) | |
}) | |
recoverSuite.add('web3.eth.accounts.recoverAddress#test', function() { | |
web3.eth.accounts.recover(message, scope.web3SignedMessage) | |
}) | |
setTimeout(async () => { | |
await setup(); | |
await verify(); | |
let signResults = await signSuite.run() | |
let recoverResults = await recoverSuite.run() | |
}, 1000); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"dependencies": { | |
"@types/web3": "^1.0.14", | |
"benchmarkify": "^2.1.0", | |
"bitcoin-ts": "^1.4.0", | |
"js-sha3": "^0.8.0", | |
"eth-crypto": "^1.2.7", | |
"lodash": "^4.17.11", | |
"secp256k1": "^3.5.2", | |
"web3": "^1.0.0-beta.36" | |
}, | |
"devDependencies": { | |
"ts-node": "^7.0.1", | |
"typescript": "^3.2.1" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Suite: ECDSA Sign | |
✔ secp256k1.sign#test 20,179 rps | |
✔ ethCrypto.sign#test 17,881 rps | |
✔ bitcoin-ts.sign#test 5,553 rps | |
✔ web3.eth.sign#test* 943 rps | |
✔ web3.eth.accounts.sign#test* 1,072 rps | |
secp256k1.sign#test 0% (20,179 rps) (avg: 49μs) | |
ethCrypto.sign#test -11.39% (17,881 rps) (avg: 55μs) | |
bitcoin-ts.sign#test -72.48% (5,553 rps) (avg: 180μs) | |
web3.eth.sign#test* -95.33% (943 rps) (avg: 1ms) | |
web3.eth.accounts.sign#test* -94.69% (1,072 rps) (avg: 933μs) | |
----------------------------------------------------------------------- | |
Suite: ECDSA Recover | |
✔ secp256k1.recoverPubkey#test 11,826 rps | |
✔ secp256k1.recoverAddress#test 10,825 rps | |
✔ ethCrypto.recoverAddress#test 8,377 rps | |
✔ bitcoin-ts.recoverPubkey#test 3,927 rps | |
✔ bitcoin-ts.recoverAddress#test 3,811 rps | |
✔ web3.eth.accounts.recoverAddress#test 453 rps | |
secp256k1.recoverPubkey#test 0% (11,826 rps) (avg: 84μs) | |
secp256k1.recoverAddress#test -8.47% (10,825 rps) (avg: 92μs) | |
ethCrypto.recoverAddress#test -29.16% (8,377 rps) (avg: 119μs) | |
bitcoin-ts.recoverPubkey#test -66.8% (3,927 rps) (avg: 254μs) | |
bitcoin-ts.recoverAddress#test -67.78% (3,811 rps) (avg: 262μs) | |
web3.eth.accounts.recoverAddress#test -96.17% (453 rps) (avg: 2ms) | |
----------------------------------------------------------------------- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment