Skip to content

Commit

Permalink
feat(jellyfish-crypto, jellyfish-transaction-builder): construct evm …
Browse files Browse the repository at this point in the history
…script (#2105)

<!--  Thanks for sending a pull request! -->

#### What this PR does / why we need it:

To construct evm script for txn builder

- add uncompressed pubkey to support evm addr
- using keccak to generate evm addr from uncompressed pubkey


#### Which issue(s) does this PR fixes?:
<!--
(Optional) Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Additional comments?:
  • Loading branch information
canonbrother authored Jul 13, 2023
1 parent b97bcd9 commit cf27d7e
Show file tree
Hide file tree
Showing 21 changed files with 297 additions and 39 deletions.
3 changes: 2 additions & 1 deletion apps/whale-api/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3.7'

services:
defi-blockchain:
image: defi/defichain:master-c14a2ba13
image: defi/defichain:master-8594d0aec
ports:
- "19554:19554"
command: >
Expand Down Expand Up @@ -42,6 +42,7 @@ services:
-grandcentralheight=16
-grandcentralepilogueheight=17
-nextnetworkupgradeheight=18
-changiintermediateheight=18
-regtest-skip-loan-collateral-validation
-regtest-minttoken-simulate-mainnet=0
Expand Down
88 changes: 88 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/jellyfish-crypto/__tests__/eth.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
8 changes: 7 additions & 1 deletion packages/jellyfish-crypto/__tests__/hash.test.ts
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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')
})
2 changes: 2 additions & 0 deletions packages/jellyfish-crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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"
Expand Down
16 changes: 16 additions & 0 deletions packages/jellyfish-crypto/src/elliptic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export interface EllipticPair {
*/
publicKey: () => Promise<Buffer>

/**
* @return {Promise<Buffer>} uncompressed public key
*/
publicKeyUncompressed: () => Promise<Buffer>

/**
* Allowed to fail if EllipticPair does not provide hardware key
* @return {Promise<Buffer>} privateKey
Expand Down Expand Up @@ -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
Expand All @@ -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<Buffer> {
Expand All @@ -62,6 +74,10 @@ class SECP256K1 implements EllipticPair {
return this.pubKey
}

async publicKeyUncompressed (): Promise<Buffer> {
return this.pubKeyUncompressed
}

async sign (hash: Buffer): Promise<Buffer> {
let signature = ecc.sign(hash, this.privKey)

Expand Down
39 changes: 39 additions & 0 deletions packages/jellyfish-crypto/src/eth.ts
Original file line number Diff line number Diff line change
@@ -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))
}
}
Loading

0 comments on commit cf27d7e

Please sign in to comment.