Last active
August 16, 2022 22:19
-
-
Save paulwongx/0e99687565b785b027660ca5a525c8c0 to your computer and use it in GitHub Desktop.
Ethers.js
This file contains 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
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | |
contract ECDSARecover { | |
using ECDSA for bytes32; | |
using ECDSA for bytes; | |
struct Coupon { | |
bytes32 r; | |
bytes32 s; | |
uint8 v; | |
} | |
enum CouponType { | |
Genesis, | |
Author, | |
Presale | |
} | |
constructor() {} | |
function recoverSignature( | |
bytes32 digest, | |
Coupon memory coupon // to be changed back | |
) external pure returns (address) { | |
address signer = ecrecover(digest, coupon.v, coupon.r, coupon.s); | |
require(signer != address(0), "ECDSA: invalid signature"); // Added check for zero address | |
return signer; | |
} | |
function getCouponType() pure external returns (CouponType) { | |
return CouponType.Genesis; | |
// >> 0 | |
} | |
function keccak256Encode() external view returns (bytes32) { | |
// https://github.com/ethers-io/ethers.js/issues/468 | |
bytes32 payloadHash = keccak256(abi.encode(msg.sender)); | |
return payloadHash; | |
} | |
// This is the message hash to recover given the inputs | |
function manualEthSignedMessage(bytes32 payloadHash) external pure returns (bytes32) { | |
// bytes32 messageHash1 = payloadHash.toEthSignedMessageHash(); | |
bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", payloadHash)); | |
return messageHash; | |
} | |
function verify(bytes32 digest, Coupon memory coupon) | |
external | |
pure | |
returns (address) | |
{ | |
// Calling ecrecover directly saves about 660 gwei | |
address signer = ecrecover(digest, coupon.v, coupon.r, coupon.s); | |
require(signer != address(0), "ECDSA: invalid signature"); | |
return signer; | |
} | |
function recover(bytes32 hash, bytes memory signature) public pure returns (address) { | |
return hash.recover(signature); | |
} | |
function toEthSignedMessageHash(bytes32 hash) public pure returns (bytes32) { | |
return hash.toEthSignedMessageHash(); | |
} | |
} |
This file contains 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 { expect } from "chai"; | |
import { expect } from "chai"; | |
import { Contract } from "ethers"; | |
import { ethers } from "hardhat"; | |
// hh test ./test/custom/ECDSARecover.test | |
// Source: https://github.com/ethers-io/ethers.js/issues/468 | |
// Source: | |
describe("ECDSARecover", function () { | |
let ecdsa: Contract; | |
beforeEach(async function () { | |
const [owner, other, another, external, adminSigner] = | |
await ethers.getSigners(); | |
this.owner = owner; | |
this.other = other; | |
this.another = another; | |
this.external = external; | |
this.adminSigner = adminSigner; | |
const ECDSA = await ethers.getContractFactory("ECDSARecover"); | |
ecdsa = await ECDSA.deploy(); | |
await ecdsa.deployed(); | |
}); | |
context("tokenURI", async function () { | |
it("Should recover ECDSA with ethers alone", async function () { | |
// >> this.adminSigner.address 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | |
// >> this.other.address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | |
const message = ethers.utils.id(this.other.address); | |
// console.log("message", message); | |
// >> message 0x417ebaa8de17c9a73acf7cf3f6c998ca2dd1e993276cc9dd37dfb7fb0cb197a4 | |
const arrayifiedMessage = ethers.utils.arrayify(message); | |
// console.log("arrayifiedMessage", arrayifiedMessage); | |
// arrayifiedMessage Uint8Array(32) [ | |
// 65, 126, 186, 168, 222, 23, 201, | |
// 167, 58, 207, 124, 243, 246, 201, | |
// 152, 202, 45, 209, 233, 147, 39, | |
// 108, 201, 221, 55, 223, 183, 251, | |
// 12, 177, 151, 164 | |
// ] | |
const signature = await this.adminSigner.signMessage(arrayifiedMessage); | |
// console.log("signature", signature); | |
// >> signature 0x92b2d891a59de570bb495ef4b6779da3024f066e60d5d7c041c35134932f7954190bbadfab3f1ea8bb5b39715a7c510a9d87e36f554782df73812a85bf50802f1c | |
// eslint-disable-next-line no-unused-vars | |
const splitSignature = ethers.utils.splitSignature(signature); | |
// console.log("splitSignature", splitSignature); | |
// splitSignature { | |
// r: '0x92b2d891a59de570bb495ef4b6779da3024f066e60d5d7c041c35134932f7954', | |
// s: '0x190bbadfab3f1ea8bb5b39715a7c510a9d87e36f554782df73812a85bf50802f', | |
// _vs: '0x990bbadfab3f1ea8bb5b39715a7c510a9d87e36f554782df73812a85bf50802f', | |
// recoveryParam: 1, | |
// v: 28, | |
// yParityAndS: '0x990bbadfab3f1ea8bb5b39715a7c510a9d87e36f554782df73812a85bf50802f', | |
// compact: '0x92b2d891a59de570bb495ef4b6779da3024f066e60d5d7c041c35134932f7954990bbadfab3f1ea8bb5b39715a7c510a9d87e36f554782df73812a85bf50802f' | |
// } | |
const recoveredSignature = ethers.utils.verifyMessage( | |
ethers.utils.arrayify(message), | |
signature | |
); | |
// console.log("recoveredSignature", recoveredSignature); | |
// >> recoveredSignature 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | |
expect(recoveredSignature).to.be.equal(this.adminSigner.address); | |
}); | |
it("Should recover ECDSA with ethers template example alone", async function () { | |
// >> this.adminSigner.address 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | |
// >> this.other.address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | |
const payload = ethers.utils.defaultAbiCoder.encode( | |
["address"], | |
[this.other.address] | |
); | |
// console.log("payload", payload); | |
// >> payload 0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8 | |
const payloadHash = ethers.utils.keccak256(payload); | |
console.log("payloadHash", payloadHash); | |
// >> payloadHash 0x7ceb58780fb137bb02223b79c88bc6404f736f8bb4d1f0895d9884122804fb73 | |
const arrayifiedMessage = ethers.utils.arrayify(payloadHash); | |
// console.log("arrayifiedMessage", arrayifiedMessage); | |
// arrayifiedMessage Uint8Array(32) [ | |
// 124, 235, 88, 120, 15, 177, 55, 187, | |
// 2, 34, 59, 121, 200, 139, 198, 64, | |
// 79, 115, 111, 139, 180, 209, 240, 137, | |
// 93, 152, 132, 18, 40, 4, 251, 115 | |
// ] | |
const signature = await this.adminSigner.signMessage(arrayifiedMessage); | |
// console.log("signature", signature); | |
// signature 0xb86d705ee7460aa646b4a948423e48b355e5be1564027f871b98197d23c3ba133b0896356c646db53a07f384a6fc3f55239fc254a206272e76e955ecc32c91401b | |
// verifyMessage adds the Ethereum header to the digest | |
const recoveredSignature = ethers.utils.verifyMessage( | |
ethers.utils.arrayify(payloadHash), | |
signature | |
); | |
// console.log("recoveredSignature", recoveredSignature); | |
// >> recoveredSignature 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | |
expect(recoveredSignature).to.be.equal(this.adminSigner.address); | |
}); | |
it("Should recover from the contract alone", async function () { | |
// >> this.adminSigner.address 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | |
// >> this.other.address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | |
const payloadHash = await ecdsa.connect(this.other).keccak256Encode(); | |
// console.log("payloadHash", payloadHash); | |
// >> payload 0x7ceb58780fb137bb02223b79c88bc6404f736f8bb4d1f0895d9884122804fb73 | |
const arrayifiedMessage = ethers.utils.arrayify(payloadHash); | |
const signature = await this.adminSigner.signMessage(arrayifiedMessage); | |
// console.log("signature", signature); | |
// >> signature 0xb86d705ee7460aa646b4a948423e48b355e5be1564027f871b98197d23c3ba133b0896356c646db53a07f384a6fc3f55239fc254a206272e76e955ecc32c91401b | |
const ethSignedPayloadHash = await ecdsa.manualEthSignedMessage( | |
payloadHash | |
); | |
// console.log("ethSignedPayloadHash", ethSignedPayloadHash); | |
// >> ethSignedPayloadHash 0x03f1224c7cac830aaed436cbba65287e9cefe9d3b5778c84c61f84cfbb9f6543 | |
const splitSignature = ethers.utils.splitSignature(signature); | |
const recoveredSignature = await ecdsa.verify( | |
ethSignedPayloadHash, | |
splitSignature | |
); | |
// console.log("recoveredSignature", recoveredSignature); | |
// >> 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | |
expect(recoveredSignature).to.be.equal(this.adminSigner.address); | |
}); | |
it.only("Should recover using ECDSA methods", async function () { | |
// >> this.other.address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | |
const payloadHash = await ecdsa.connect(this.other).keccak256Encode(); | |
// >> payload 0x7ceb58780fb137bb02223b79c88bc6404f736f8bb4d1f0895d9884122804fb73 | |
const arrayifiedMessage = ethers.utils.arrayify(payloadHash); | |
const signature = await this.adminSigner.signMessage(arrayifiedMessage); | |
// console.log("signature", signature); | |
const ethSignedPayloadHash = await ecdsa.toEthSignedMessageHash( | |
payloadHash | |
); | |
// console.log("ethSignedPayloadHash", ethSignedPayloadHash); | |
// >> ethSignedPayloadHash 0x03f1224c7cac830aaed436cbba65287e9cefe9d3b5778c84c61f84cfbb9f6543 | |
const recoveredSignature = await ecdsa.recover( | |
ethSignedPayloadHash, | |
signature | |
); | |
// console.log("recoveredSignature", recoveredSignature); | |
// >> recoveredSignature 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | |
expect(recoveredSignature).to.be.equal(this.adminSigner.address); | |
}); | |
it("Recovering the signatures", async function () { | |
console.log("this.other.address", this.other.address); | |
// >> this.other.address 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | |
console.log("this.adminSigner.address", this.adminSigner.address); | |
// >> this.adminSigner.address 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | |
}); | |
}); | |
}); |
This file contains 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
const { ethers } = require("ethers"); | |
const privateKey = | |
"0x0123456789012345678901234567890123456789012345678901234567890123"; | |
const wallet = new ethers.Wallet(privateKey); | |
// console.log("signer address", wallet.address); | |
// >> 0x14791697260E4c9A71f18484C9f997B308e59325 | |
// export function keccak256(types: ReadonlyArray<string>, values: ReadonlyArray<any>) { | |
// return hashKeccak256(pack(types, values)); | |
// } | |
const solidityKeccak256 = async (message) => { | |
const messageHash = ethers.utils.solidityKeccak256(["string"], [message]); | |
// console.log("messageHash: ", messageHash); | |
return messageHash; | |
}; | |
// export function keccak256(data: BytesLike): string { | |
// return '0x' + sha3.keccak_256(arrayify(data)); | |
// } | |
const keccak256 = async (message) => { | |
const messageHash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(message)); | |
// console.log("messageHash: ", messageHash); | |
return messageHash; | |
}; | |
// export function id(text: string): string { | |
// return keccak256(toUtf8Bytes(text)); | |
// } | |
const id = async (message) => { | |
const messageHash = ethers.utils.id(message); | |
// console.log("messageHash: ", messageHash); | |
return messageHash; | |
}; | |
const signAndCompare = async (messageHash) => { | |
const signature = await wallet.signMessage( | |
ethers.utils.arrayify(messageHash) | |
); | |
// console.log("signature:", signature); | |
// >> 0xe0400ffd1dce1f2076c812fcbdb622c3d5a49916540292d43fae7e400aba64e52695742c4810a3f27eb55deec5adfc287eae9e9442d257867be1f1210c895f311b | |
const arrayified = ethers.utils.arrayify(messageHash); | |
// console.log("arrayified", arrayified); | |
// >> Uint8Array(32) [ | |
// 71, 23, 50, 133, 168, 215, 52, 30, | |
// 94, 151, 47, 198, 119, 40, 99, 132, | |
// 248, 2, 248, 239, 66, 165, 236, 95, | |
// 3, 187, 250, 37, 76, 176, 31, 173 | |
// ] | |
const recoverAddress = ethers.utils.verifyMessage(arrayified, signature); | |
return recoverAddress === wallet.address; | |
}; | |
const toUtf8Bytes = async (message) => { | |
const utf8Bytes = ethers.utils.toUtf8Bytes(message); | |
// console.log("utf8Bytes", utf8Bytes); | |
return utf8Bytes; | |
}; | |
const hexlify = async (utf8Bytes) => { | |
const hexString = ethers.utils.hexlify(utf8Bytes); | |
// console.log("hexString", hexString); | |
return hexString; | |
}; | |
const arrayify = async (hexString) => { | |
const uint8Array = ethers.utils.arrayify(hexString); | |
console.log("uint8Array", uint8Array); | |
return uint8Array; | |
}; | |
/** | |
* Keccack always returns a 32 byte array | |
* The message hash has 64 characters because you need two characters to represent 1 byte | |
* Each character is 8 bits or 1 byte | |
*/ | |
solidityKeccak256("ABC") | |
.then(signAndCompare) | |
.then((authentic) => console.log(`authentic ${authentic}`)); | |
// >> messageHash: 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8 | |
// >> true | |
keccak256("ABC") | |
.then(signAndCompare) | |
.then((authentic) => console.log(`authentic ${authentic}`)); | |
// >> messageHash: 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8 | |
// >> true | |
id("ABC") | |
.then(signAndCompare) | |
.then((authentic) => console.log(`authentic ${authentic}`)); | |
// >> messageHash: 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8 | |
// >> true | |
/** | |
* UTF-8: Unicode Transformation Format-8 | |
* These functions show the decimal equivalent of characters | |
* utf8 Table: https://www.charset.org/utf-8 | |
*/ | |
toUtf8Bytes(123); // <- Must be text | |
// >> utf8Bytes Uint8Array(0) [] | |
toUtf8Bytes("0123"); | |
// >> Uint8Array(4) [ 48, 49, 50, 51 ] | |
toUtf8Bytes("abc"); | |
// >> Uint8Array(3) [ 97, 98, 99 ] | |
toUtf8Bytes("ABC"); | |
// >> Uint8Array(3) [ 65, 66, 67 ] | |
/** | |
* These functions show hex string in the format of 0x + UTF-8 Hex characters | |
* For example, A is 65 in utf8 and 41 in UTF8-Hex | |
* utf8-Hex can be in lowercase, it's all the same | |
* utf8 Table: https://www.charset.org/utf-8 | |
*/ | |
toUtf8Bytes(123).then(hexlify); | |
// >> 0x | |
toUtf8Bytes("0123").then(hexlify); | |
// >> 0x30313233 | |
toUtf8Bytes("abc").then(hexlify); | |
// >> 0x616263 | |
toUtf8Bytes("ABC").then(hexlify); | |
// >> 0x414243 | |
toUtf8Bytes("JKL").then(hexlify); | |
// >> 0x4a4b4c | |
toUtf8Bytes("ABC").then(hexlify).then(arrayify); | |
// >> Uint8Array(3) [ 65, 66, 67 ] | |
toUtf8Bytes("ABC").then(arrayify); | |
// >> Uint8Array(3) [ 65, 66, 67 ] |
This file contains 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
/* eslint-disable node/no-unpublished-require */ | |
const { ethers } = require("ethers"); | |
const Web3 = require("web3"); | |
const compare = (data1, data2) => { | |
return data1 === data2; | |
}; | |
const hexToBytes = async (sig) => { | |
const wBytes = Web3.utils.hexToBytes(sig); | |
// console.log("wBytes", wBytes); | |
const eBytes = ethers.utils.arrayify(sig); | |
// console.log("eBytes", eBytes); | |
return ( | |
wBytes.length === eBytes.length && wBytes.every((v, i) => v === eBytes[i]) | |
); | |
}; | |
const bytesToHex = async (bytes) => { | |
const wHex = Web3.utils.bytesToHex(bytes); | |
// console.log("wHex", wHex); | |
const eHex = ethers.utils.hexlify(bytes); | |
// console.log("eHex", eHex); | |
return compare(wHex, eHex); | |
}; | |
const sha3 = async (message) => { | |
const wsha3 = Web3.utils.sha3(message); | |
// console.log("wsha3", wsha3); | |
const esha3 = ethers.utils.id(message); | |
// console.log("esha3", esha3); | |
return compare(wsha3, esha3); | |
}; | |
const sign = async (message, privateKey) => { | |
const web3 = new Web3(); | |
const wHash = Web3.utils.sha3(message); | |
const wSignature = await web3.eth.accounts.sign(wHash, privateKey); | |
// console.log("wSignature", wSignature); | |
// wSignature { | |
// message: '0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8', | |
// messageHash: '0x6f5fabc4608e7289cbb18e2dfd8fa000a2efacbf112e061ccfa4778e4ea79ef6', | |
// v: '0x1b', | |
// r: '0x4c82baef8342bf38d0834816f3a62107f925aa1ca0aea176bd10075dba0f660b', | |
// s: '0x75c7ec29efc8b2e6421662c3105c194f20a10ff64ec5e5a628e482405c861c34', | |
// signature: '0x4c82baef8342bf38d0834816f3a62107f925aa1ca0aea176bd10075dba0f660b75c7ec29efc8b2e6421662c3105c194f20a10ff64ec5e5a628e482405c861c341b' | |
// } | |
// console.log("wSignature.signature", wSignature.signature); | |
const signer = new ethers.Wallet(privateKey); | |
const eHash = ethers.utils.id(message); | |
const eSignature = await signer.signMessage(ethers.utils.arrayify(eHash)); | |
// console.log("eSignature", eSignature); | |
const splitESignature = ethers.utils.splitSignature(eSignature); | |
// console.log("splitESignature", splitESignature); | |
// splitESignature { | |
// r: '0x4c82baef8342bf38d0834816f3a62107f925aa1ca0aea176bd10075dba0f660b', | |
// s: '0x75c7ec29efc8b2e6421662c3105c194f20a10ff64ec5e5a628e482405c861c34', | |
// _vs: '0x75c7ec29efc8b2e6421662c3105c194f20a10ff64ec5e5a628e482405c861c34', | |
// recoveryParam: 0, | |
// v: 27, | |
// yParityAndS: '0x75c7ec29efc8b2e6421662c3105c194f20a10ff64ec5e5a628e482405c861c34', | |
// compact: '0x4c82baef8342bf38d0834816f3a62107f925aa1ca0aea176bd10075dba0f660b75c7ec29efc8b2e6421662c3105c194f20a10ff64ec5e5a628e482405c861c34' | |
// } | |
return compare(wSignature.signature, eSignature); | |
}; | |
sha3("ABC"); | |
// wsha3 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8 | |
// esha3 0xe1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8 | |
bytesToHex(ethers.utils.toUtf8Bytes("ABC")); | |
// wHex 0x414243 | |
// eHex 0x414243 | |
hexToBytes("0x414243"); | |
// wBytes [ 65, 66, 67 ] | |
// eBytes Uint8Array(3) [ 65, 66, 67 ] | |
const privateKey = | |
"0x0123456789012345678901234567890123456789012345678901234567890123"; | |
sign("ABC", privateKey).then(console.log); | |
// const sig = web3.eth.accounts.sign("ABC", privateKey); | |
const signer = new ethers.Wallet(privateKey); | |
// console.log( | |
// "signer._mnemonic", | |
// signer._signingKey((x) => x) | |
// ); | |
// SigningKey { | |
// curve: 'secp256k1', | |
// privateKey: '0x0123456789012345678901234567890123456789012345678901234567890123', | |
// publicKey: '0x046655feed4d214c261e0a6b554395596f1f1476a77d999560e5a8df9b8a1a3515217e88dd05e938efdd71b2cce322bf01da96cd42087b236e8f5043157a9c068e', | |
// compressedPublicKey: '0x026655feed4d214c261e0a6b554395596f1f1476a77d999560e5a8df9b8a1a3515', | |
// _isSigningKey: true | |
// } | |
/** Initiating a contract for testing */ | |
// ethers | |
function ethersTestContract() { | |
describe("ECDSA", function () { | |
let other; // typeof SignerWithAddress | |
let ecdsa; // typeof Contract | |
beforeEach(async function () { | |
const ECDSAMock = await ethers.getContractFactory("ECDSAMock"); | |
ecdsa = await ECDSAMock.deploy(); | |
await ecdsa.deployed(); | |
this.ecdsa = ecdsa; | |
[other] = await ethers.getSigners(); | |
}); | |
}); | |
} | |
// web3 | |
function web3TestContract() { | |
describe("ECDSA", function (accounts) { | |
const [other] = accounts; | |
beforeEach(async function () { | |
const ECDSAMock = artifacts.require("ECDSAMock"); | |
this.ecdsa = await ECDSAMock.new(); | |
}); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment