diff --git a/apps/whale-api/docker-compose.yml b/apps/whale-api/docker-compose.yml index 512e2e4ea3..224ce74ecd 100644 --- a/apps/whale-api/docker-compose.yml +++ b/apps/whale-api/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.7' services: defi-blockchain: - image: defi/defichain:master-c14a2ba13 + image: defi/defichain:master-8594d0aec ports: - "19554:19554" command: > @@ -42,6 +42,7 @@ services: -grandcentralheight=16 -grandcentralepilogueheight=17 -nextnetworkupgradeheight=18 + -changiintermediateheight=18 -regtest-skip-loan-collateral-validation -regtest-minttoken-simulate-mainnet=0 diff --git a/package-lock.json b/package-lock.json index 265834d5f4..af857c4183 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7089,6 +7089,15 @@ "license": "MIT", "peer": true }, + "node_modules/@types/keccak": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/keccak/-/keccak-3.0.1.tgz", + "integrity": "sha512-/MxAVmtyyeOvZ6dGf3ciLwFRuV5M8DRIyYNFGHYI6UyBW4/XqyO0LZw+JFMvaeY3cHItQAkELclBU1x5ank6mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/level": { "version": "6.0.1", "dev": true, @@ -16418,6 +16427,20 @@ "integrity": "sha512-dgFenZnMsc1xGNqgdtgnh7DK+Oy352CE3VZLbzcbQpsBs9iI2K3M0IRrdgREZ72eItTjbl0suRyvKRdVQa9GbA==", "dev": true }, + "node_modules/keccak": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", + "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/keyv": { "version": "3.1.0", "license": "MIT", @@ -18272,6 +18295,11 @@ "integrity": "sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw==", "dev": true }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, "node_modules/node-emoji": { "version": "1.11.0", "license": "MIT", @@ -26030,6 +26058,7 @@ "browserify-aes": "^1.2.0", "bs58": "^4.0.1", "create-hash": "^1.2.0", + "keccak": "^3.0.3", "randombytes": "^2.1.0", "tiny-secp256k1": "^1.1.6", "wif": "^2.0.6" @@ -26038,6 +26067,7 @@ "@types/bech32": "1.1.4", "@types/bs58": "4.0.1", "@types/create-hash": "1.2.2", + "@types/keccak": "^3.0.1", "@types/randombytes": "2.0.0", "@types/tiny-secp256k1": "1.0.0", "@types/wif": "2.0.2" @@ -26185,6 +26215,8 @@ "version": "0.0.0", "license": "MIT", "dependencies": { + "@defichain/jellyfish-transaction": "^0.0.0", + "@defichain/jellyfish-wallet": "^0.0.0", "bip32": "^2.0.6", "bip39": "^3.0.4", "create-hmac": "^1.1.7" @@ -28080,6 +28112,7 @@ "@types/bech32": "1.1.4", "@types/bs58": "4.0.1", "@types/create-hash": "1.2.2", + "@types/keccak": "^3.0.1", "@types/randombytes": "2.0.0", "@types/tiny-secp256k1": "1.0.0", "@types/wif": "2.0.2", @@ -28088,6 +28121,7 @@ "browserify-aes": "^1.2.0", "bs58": "^4.0.1", "create-hash": "^1.2.0", + "keccak": "^3.0.3", "randombytes": "^2.1.0", "tiny-secp256k1": "^1.1.6", "wif": "^2.0.6" @@ -28181,6 +28215,8 @@ "@defichain/jellyfish-wallet-mnemonic": { "version": "file:packages/jellyfish-wallet-mnemonic", "requires": { + "@defichain/jellyfish-transaction": "^0.0.0", + "@defichain/jellyfish-wallet": "^0.0.0", "@types/create-hmac": "1.1.0", "bip32": "^2.0.6", "bip39": "^3.0.4", @@ -31696,6 +31732,15 @@ "dev": true, "peer": true }, + "@types/keccak": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/keccak/-/keccak-3.0.1.tgz", + "integrity": "sha512-/MxAVmtyyeOvZ6dGf3ciLwFRuV5M8DRIyYNFGHYI6UyBW4/XqyO0LZw+JFMvaeY3cHItQAkELclBU1x5ank6mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/level": { "version": "6.0.1", "dev": true, @@ -36364,6 +36409,7 @@ "@types/bech32": "1.1.4", "@types/bs58": "4.0.1", "@types/create-hash": "1.2.2", + "@types/keccak": "^3.0.1", "@types/randombytes": "2.0.0", "@types/tiny-secp256k1": "1.0.0", "@types/wif": "2.0.2", @@ -36372,6 +36418,7 @@ "browserify-aes": "^1.2.0", "bs58": "^4.0.1", "create-hash": "^1.2.0", + "keccak": "^3.0.3", "randombytes": "^2.1.0", "tiny-secp256k1": "^1.1.6", "wif": "^2.0.6" @@ -36465,6 +36512,8 @@ "@defichain/jellyfish-wallet-mnemonic": { "version": "file:packages/jellyfish-wallet-mnemonic", "requires": { + "@defichain/jellyfish-transaction": "^0.0.0", + "@defichain/jellyfish-wallet": "^0.0.0", "@types/create-hmac": "1.1.0", "bip32": "^2.0.6", "bip39": "^3.0.4", @@ -39980,6 +40029,15 @@ "dev": true, "peer": true }, + "@types/keccak": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/keccak/-/keccak-3.0.1.tgz", + "integrity": "sha512-/MxAVmtyyeOvZ6dGf3ciLwFRuV5M8DRIyYNFGHYI6UyBW4/XqyO0LZw+JFMvaeY3cHItQAkELclBU1x5ank6mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/level": { "version": "6.0.1", "dev": true, @@ -46430,6 +46488,16 @@ "integrity": "sha512-dgFenZnMsc1xGNqgdtgnh7DK+Oy352CE3VZLbzcbQpsBs9iI2K3M0IRrdgREZ72eItTjbl0suRyvKRdVQa9GbA==", "dev": true }, + "keccak": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", + "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + } + }, "keyv": { "version": "3.1.0", "requires": { @@ -47750,6 +47818,11 @@ "integrity": "sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw==", "dev": true }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, "node-emoji": { "version": "1.11.0", "requires": { @@ -56521,6 +56594,16 @@ "integrity": "sha512-dgFenZnMsc1xGNqgdtgnh7DK+Oy352CE3VZLbzcbQpsBs9iI2K3M0IRrdgREZ72eItTjbl0suRyvKRdVQa9GbA==", "dev": true }, + "keccak": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", + "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + } + }, "keyv": { "version": "3.1.0", "requires": { @@ -57841,6 +57924,11 @@ "integrity": "sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw==", "dev": true }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, "node-emoji": { "version": "1.11.0", "requires": { diff --git a/packages/jellyfish-api-core/__tests__/category/masternode/updatemasternode.test.ts b/packages/jellyfish-api-core/__tests__/category/masternode/updatemasternode.test.ts index 87165044c6..03cdfb7ac9 100644 --- a/packages/jellyfish-api-core/__tests__/category/masternode/updatemasternode.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/masternode/updatemasternode.test.ts @@ -309,14 +309,15 @@ describe('Update Masternode', () => { await expect(promise).rejects.toThrow(`operatorAddress (${operatorAddress}) does not refer to a P2PKH or P2WPKH address`) } - { - const rewardAddress = await client.wallet.getNewAddress('', AddressType.P2SH_SEGWIT) - const promise = client.masternode.updateMasternode(masternodeId, { - rewardAddress - }) - await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow('Reward address must be P2PKH or P2WPKH type\', code: -32600, method: updatemasternode') - } + // Updated: P2SH is allowed for rewardAddress - https://github.com/DeFiCh/ain/pull/1664 + // { + // const rewardAddress = await client.wallet.getNewAddress('', AddressType.P2SH_SEGWIT) + // const promise = client.masternode.updateMasternode(masternodeId, { + // rewardAddress + // }) + // await expect(promise).rejects.toThrow(RpcApiError) + // await expect(promise).rejects.toThrow('Reward address must be P2PKH or P2WPKH type\', code: -32600, method: updatemasternode') + // } }) it('should be failed as invalid address is not allowed', async () => { diff --git a/packages/jellyfish-crypto/__tests__/eth.test.ts b/packages/jellyfish-crypto/__tests__/eth.test.ts new file mode 100755 index 0000000000..7760e95095 --- /dev/null +++ b/packages/jellyfish-crypto/__tests__/eth.test.ts @@ -0,0 +1,20 @@ +import { Eth } from '../src' + +const keypair = { + evmAddr: '0x0a06de8abc3f15359ec0dfe32394c8b8f09e828f', + checksumEvmAddr: '0x0a06DE8AbC3f15359EC0dfe32394C8B8f09e828F', + pubKeyUncompressed: '04e60942751bc776912cdc8cf11aa3ce33ce3ef6882ff93a9fafa0b968e6b926293a6913f9efae6362ce8ffd0b8a4ae45c3a6ccafacbab2192991125277d6710db' +} + +it('should reject invalid length uncompressed public key', () => { + expect(() => { + // @ts-expect_error + Eth.fromPubKeyUncompressed(Buffer.from(keypair.pubKeyUncompressed, 'hex').subarray(1)) + }).toThrow('InvalidUncompressedPubKeyLength') +}) + +it('should convert evm address to checksum address', () => { + const pubKeyUncompressed = Buffer.from(keypair.pubKeyUncompressed, 'hex') + const checksumEvmAddr = Eth.fromPubKeyUncompressed(pubKeyUncompressed) + expect(checksumEvmAddr).toStrictEqual(keypair.checksumEvmAddr) +}) diff --git a/packages/jellyfish-crypto/__tests__/hash.test.ts b/packages/jellyfish-crypto/__tests__/hash.test.ts index c4ed99f0d3..1e9af6fa76 100644 --- a/packages/jellyfish-crypto/__tests__/hash.test.ts +++ b/packages/jellyfish-crypto/__tests__/hash.test.ts @@ -1,4 +1,4 @@ -import { dSHA256, HASH160, SHA256 } from '../src' +import { dSHA256, HASH160, KECCAK256, SHA256 } from '../src' it('should sha256 with SHA256', () => { const buffer = Buffer.from('56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae', 'hex') @@ -17,3 +17,9 @@ it('should double sha256 with dSHA256', () => { const hashed = dSHA256(buffer) expect(hashed.toString('hex')).toStrictEqual('52b0a642eea2fb7ae638c36f6252b6750293dbe574a806984b8e4d8548339a3b') }) + +it('should keccak256 with KECCAK256', () => { + const buffer = Buffer.from('1286647f7440111ab928bdea4daa42533639c4567d81eca0fff622fb6438eae31cee4e0c53581dacc579fde09f5a25150703e9efd8d2c5ecbbda619d4ca104e6', 'hex') + const hashed = KECCAK256(buffer) + expect(hashed.toString('hex')).toStrictEqual('8bf885ce24b542db49bade8e9b8a4af42140d8a4c153a822f02571a1dd037e89') +}) diff --git a/packages/jellyfish-crypto/package.json b/packages/jellyfish-crypto/package.json index 48d37603eb..9649e2485b 100644 --- a/packages/jellyfish-crypto/package.json +++ b/packages/jellyfish-crypto/package.json @@ -20,6 +20,7 @@ "bs58": "^4.0.1", "create-hash": "^1.2.0", "randombytes": "^2.1.0", + "keccak": "^3.0.3", "tiny-secp256k1": "^1.1.6", "wif": "^2.0.6" }, @@ -30,6 +31,7 @@ "@types/bech32": "1.1.4", "@types/bs58": "4.0.1", "@types/create-hash": "1.2.2", + "@types/keccak": "^3.0.1", "@types/randombytes": "2.0.0", "@types/tiny-secp256k1": "1.0.0", "@types/wif": "2.0.2" diff --git a/packages/jellyfish-crypto/src/elliptic.ts b/packages/jellyfish-crypto/src/elliptic.ts index 766309e491..450ce84da5 100644 --- a/packages/jellyfish-crypto/src/elliptic.ts +++ b/packages/jellyfish-crypto/src/elliptic.ts @@ -16,6 +16,11 @@ export interface EllipticPair { */ publicKey: () => Promise + /** + * @return {Promise} uncompressed public key + */ + publicKeyUncompressed: () => Promise + /** * Allowed to fail if EllipticPair does not provide hardware key * @return {Promise} privateKey @@ -44,6 +49,7 @@ export interface EllipticPair { class SECP256K1 implements EllipticPair { private readonly privKey: Buffer private readonly pubKey: Buffer + private readonly pubKeyUncompressed: Buffer constructor (privKey: Buffer) { this.privKey = privKey @@ -52,6 +58,12 @@ class SECP256K1 implements EllipticPair { throw new Error('point at infinity') } this.pubKey = pubKey + + const pubKeyUncompressed = ecc.pointFromScalar(privKey, false) + if (pubKeyUncompressed === null) { + throw new Error('point at infinity') + } + this.pubKeyUncompressed = pubKeyUncompressed } async privateKey (): Promise { @@ -62,6 +74,10 @@ class SECP256K1 implements EllipticPair { return this.pubKey } + async publicKeyUncompressed (): Promise { + return this.pubKeyUncompressed + } + async sign (hash: Buffer): Promise { let signature = ecc.sign(hash, this.privKey) diff --git a/packages/jellyfish-crypto/src/eth.ts b/packages/jellyfish-crypto/src/eth.ts new file mode 100644 index 0000000000..f5eb05df6d --- /dev/null +++ b/packages/jellyfish-crypto/src/eth.ts @@ -0,0 +1,39 @@ +import createKeccakHash from 'keccak' +import { KECCAK256 } from './hash' + +// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md +function toChecksumAddress (address: string): string { + address = address.toLowerCase().replace('0x', '') + const hash = createKeccakHash('keccak256').update(address).digest('hex') + let checksum = '0x' + + for (let i = 0; i < address.length; i += 1) { + parseInt(hash[i], 16) >= 8 + ? checksum += address[i].toUpperCase() + : checksum += address[i] + } + + return checksum +} + +function toAddress (hash: Buffer): string { + const hex = hash.toString('hex') + // grab the last 20 bytes (40 chars) + const sliced = hex.slice(hex.length - 40) + return `0x${sliced}` +} + +export const Eth = { + /** + * @param {Buffer} uncompressed pubKey to format into Eth + * @return {string} eth encoded address + */ + fromPubKeyUncompressed (pubKeyUncompressed: Buffer): string { + if (pubKeyUncompressed.length !== 65) { + throw new Error('InvalidUncompressedPubKeyLength') + } + const sub = pubKeyUncompressed.subarray(1, 65) + const hash = KECCAK256(sub) + return toChecksumAddress(toAddress(hash)) + } +} diff --git a/packages/jellyfish-crypto/src/hash.ts b/packages/jellyfish-crypto/src/hash.ts index 2f0d6524d6..905616987f 100644 --- a/packages/jellyfish-crypto/src/hash.ts +++ b/packages/jellyfish-crypto/src/hash.ts @@ -1,4 +1,5 @@ import createHash from 'create-hash' +import createKeccakHash from 'keccak' /** * @param {Buffer} buffer to RIPEMD160(buffer) @@ -27,3 +28,10 @@ export function HASH160 (buffer: Buffer): Buffer { export function dSHA256 (buffer: Buffer): Buffer { return SHA256(SHA256(buffer)) } + +/** + * @param {Buffer} buffer to KECCAK256(buffer) + */ +export function KECCAK256 (buffer: Buffer): Buffer { + return createKeccakHash('keccak256').update(buffer).digest() +} diff --git a/packages/jellyfish-crypto/src/index.ts b/packages/jellyfish-crypto/src/index.ts index 3ec447263a..08abed4fc7 100644 --- a/packages/jellyfish-crypto/src/index.ts +++ b/packages/jellyfish-crypto/src/index.ts @@ -5,6 +5,7 @@ export * from './aes256' export * from './bech32' export * from './bs58' export * from './der' +export * from './eth' export * from './elliptic' export * from './hash' export * from './wif' diff --git a/packages/jellyfish-transaction-builder/__tests__/provider.mock.ts b/packages/jellyfish-transaction-builder/__tests__/provider.mock.ts index ee05e985b6..b8a1dd4d55 100644 --- a/packages/jellyfish-transaction-builder/__tests__/provider.mock.ts +++ b/packages/jellyfish-transaction-builder/__tests__/provider.mock.ts @@ -1,5 +1,5 @@ import BigNumber from 'bignumber.js' -import { Bech32, EllipticPair, HASH160, WIF } from '@defichain/jellyfish-crypto' +import { Bech32, EllipticPair, Eth, HASH160, WIF } from '@defichain/jellyfish-crypto' import { EllipticPairProvider, FeeRateProvider, Prevout, PrevoutProvider } from '../src' import { MasterNodeRegTestContainer } from '@defichain/testcontainers' import { OP_CODES, Script } from '@defichain/jellyfish-transaction' @@ -86,6 +86,16 @@ export class MockEllipticPairProvider implements EllipticPairProvider { } } + async evmScript (): Promise