Skip to content

Commit

Permalink
guess P2SH / P2PKH / P2WSH / P2WPKH type (#189)
Browse files Browse the repository at this point in the history
Co-authored-by: Fuxing Loh <[email protected]>
Co-authored-by: Fuxing Loh <[email protected]>
  • Loading branch information
3 people authored May 17, 2021
1 parent 7f906b1 commit 1bfeab9
Show file tree
Hide file tree
Showing 26 changed files with 1,251 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/website/ @fuxingloh

/packages/jellyfish/ @fuxingloh
/packages/jellyfish-address/ @fuxingloh @ivan-zynesis
/packages/jellyfish-api-core/ @fuxingloh @canonbrother @jingyi2811
/packages/jellyfish-api-jsonrpc/ @fuxingloh
/packages/jellyfish-crypto/ @fuxingloh
Expand Down
1 change: 1 addition & 0 deletions .github/governance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ issue:
- workflow
- website
- jellyfish
- jellyfish-address
- jellyfish-api-core
- jellyfish-api-jsonrpc
- jellyfish-crypto
Expand Down
6 changes: 5 additions & 1 deletion .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ labels:
matcher:
files: "packages/jellyfish/**"

- label: area/jellyfish-address
sync: true
matcher:
files: "packages/jellyfish-address/**"

- label: area/jellyfish-api-core
sync: true
matcher:
Expand Down Expand Up @@ -51,7 +56,6 @@ labels:
matcher:
files: "packages/jellyfish-wallet-mnemonic/**"


- label: area/testcontainers
sync: true
matcher:
Expand Down
2 changes: 2 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
name: area/website
- color: fbca04
name: area/jellyfish
- color: fbca04
name: area/jellyfish-address
- color: fbca04
name: area/jellyfish-api-core
- color: fbca04
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ node_modules
dist
*.tgz

# debug log
lerna-debug.log

coverage
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ version tag.
Package | Description
---------------------------------------------------|-------------
`@defichain/jellyfish` | Library bundled usage entrypoint with conventional defaults for 4 bundles: umd, esm, cjs and d.ts
`@defichain/jellyfish-address` | Provide address builder, parser, validator utility library for DeFi Blockchain.
`@defichain/jellyfish-api-core` | A protocol agnostic DeFi Blockchain client interfaces, with a "foreign function interface" design.
`@defichain/jellyfish-api-jsonrpc` | Implements the [JSON-RPC 1.0](https://www.jsonrpc.org/specification_v1) specification for api-core.
`@defichain/jellyfish-crypto` | Cryptography operations for jellyfish, includes a simple 'secp256k1' EllipticPair.
Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

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

6 changes: 6 additions & 0 deletions packages/jellyfish-address/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[![npm](https://img.shields.io/npm/v/@defichain/jellyfish-address)](https://www.npmjs.com/package/@defichain/jellyfish-address/v/latest)
[![npm@next](https://img.shields.io/npm/v/@defichain/jellyfish-address/next)](https://www.npmjs.com/package/@defichain/jellyfish-address/v/next)

# @defichain/jellyfish-address

DeFi blockchain address type builder, parser, validator library.
57 changes: 57 additions & 0 deletions packages/jellyfish-address/__tests__/base58_address.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Script } from '@defichain/jellyfish-transaction/src/tx'
import { Network } from '@defichain/jellyfish-network'
import { Base58Address } from '../src'

class DummyB58Address extends Base58Address {
getScript (): Script {
return {
stack: []
}
}

getPrefix (): number {
return this.network.pubKeyHashPrefix // match the fixture p2pkh prefix
}
}

describe('Base58Address', () => {
const b58Fixture = {
p2sh: 'dFFPENo7FPMJpDV6fUcfo4QfkZrfrV1Uf8', // prefix = 0x12
p2pkh: '8JBuS81VT8ouPrT6YS55qoS74D13Cw7h1Y'
}

const dummyNetwork: Network = {
name: 'regtest',
bech32: {
hrp: 'dummy'
},
bip32: {
publicPrefix: 0x00000000,
privatePrefix: 0x00000000
},
wifPrefix: 0x00,
pubKeyHashPrefix: 0x12,
scriptHashPrefix: 0x00,
messagePrefix: '\x00Dummy Msg Prefix:\n'
}

describe('extensible, should work for any defined network protocol', () => {
it('fromAddress() - valid', () => {
const valid = Base58Address.fromAddress<DummyB58Address>(dummyNetwork, b58Fixture.p2pkh, DummyB58Address)
expect(valid.validate()).toBeTruthy()
})

it('fromAddress() - invalid character set', () => {
const invalid = Base58Address.fromAddress<DummyB58Address>(dummyNetwork, 'invalid b58 address', DummyB58Address)
expect(invalid.validate()).toBeFalsy()
})

it('fromAddress() - invalid prefix', () => {
const invalid = Base58Address.fromAddress<DummyB58Address>(dummyNetwork, b58Fixture.p2sh, DummyB58Address)
expect(invalid.validate()).toBeFalsy()

const valid = Base58Address.fromAddress<DummyB58Address>(dummyNetwork, b58Fixture.p2pkh, DummyB58Address)
expect(valid.validate()).toBeTruthy()
})
})
})
51 changes: 51 additions & 0 deletions packages/jellyfish-address/__tests__/bech32_address.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Script } from '@defichain/jellyfish-transaction/src/tx'
import { Network } from '@defichain/jellyfish-network'
import { Bech32Address } from '../src'

class DummyBech32Address extends Bech32Address {
getScript (): Script {
return {
stack: []
}
}
}

describe('Bech32Address', () => {
const bech32Fixture = {
p2wpkh: 'dummy1qpe7q4vvtxpdunpazvmwqdh3xlnatfdt2xr8mpv', // edited prefix to match test network
invalidPrefix: 'prefix1qpe7q4vvtxpdunpazvmwqdh3xlnatfdt2xr8mpv', // original p2wpkh address sample
invalidCharset: 'dummy1qpe7q4vvtxpdunpazvmwqdh3xlnatfdt2xr8mpo' // character 'o'
}

const dummyNetwork: Network = {
name: 'regtest',
bech32: {
hrp: 'dummy'
},
bip32: {
publicPrefix: 0x00000000,
privatePrefix: 0x00000000
},
wifPrefix: 0x00,
pubKeyHashPrefix: 0x00,
scriptHashPrefix: 0x00,
messagePrefix: '\x00Dummy Msg Prefix:\n'
}

describe('extensible, should work for any defined network protocol', () => {
it('fromAddress() - valid', () => {
const valid = Bech32Address.fromAddress<DummyBech32Address>(dummyNetwork, bech32Fixture.p2wpkh, DummyBech32Address)
expect(valid.validate()).toBeTruthy()
})

it('fromAddress() - invalid character set', () => {
const invalid = Bech32Address.fromAddress<DummyBech32Address>(dummyNetwork, bech32Fixture.invalidCharset, DummyBech32Address)
expect(invalid.validate()).toBeFalsy()
})

it('fromAddress() - invalid prefix', () => {
const invalid = Bech32Address.fromAddress<DummyBech32Address>(dummyNetwork, bech32Fixture.invalidPrefix, DummyBech32Address)
expect(invalid.validate()).toBeFalsy()
})
})
})
142 changes: 142 additions & 0 deletions packages/jellyfish-address/__tests__/p2pkh.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import bs58 from 'bs58'
import { MainNet, RegTest, TestNet } from '@defichain/jellyfish-network'
import { OP_CODES } from '@defichain/jellyfish-transaction/src/script'
import { RegTestContainer } from '@defichain/testcontainers'
import * as DeFiAddress from '../src'
import { P2PKH } from '../src'

describe('P2PKH', () => {
const container = new RegTestContainer()
const p2pkhFixture = {
mainnet: '8JBuS81VT8ouPrT6YS55qoS74D13Cw7h1Y',
testnet: '7LMorkhKTDjbES6DfRxX2RiNMbeemUkxmp',
regtest: '',

invalid: 'JBuS81VT8ouPrT6YS55qoS74D13Cw7h1Y', // edited, removed prefix
invalidChecksum: '8JBuS81VT8ouPrT6YS55qoS74D13Cw7h1X' // edited checksum (last char)
}

beforeAll(async () => {
await container.start()
await container.waitForReady()
p2pkhFixture.regtest = await container.getNewAddress('', 'legacy')
})

afterAll(async () => await container.stop())

describe('from() - valid address', () => {
it('should get the type precisely', () => {
const p2pkh = DeFiAddress.from('mainnet', p2pkhFixture.mainnet)
expect(p2pkh.valid).toBeTruthy()
expect(p2pkh.type).toBe('P2PKH')
expect(p2pkh.constructor.name).toBe('P2PKH')
expect(p2pkh.network).toBe(MainNet)
})

it('should work for all recognized network type', () => {
const testnet = DeFiAddress.from('testnet', p2pkhFixture.testnet)
expect(testnet.valid).toBeTruthy()
expect(testnet.type).toBe('P2PKH')
expect(testnet.constructor.name).toBe('P2PKH')
expect(testnet.network).toBe(TestNet)

const regtest = DeFiAddress.from('regtest', p2pkhFixture.regtest)
expect(regtest.valid).toBeTruthy()
expect(regtest.type).toBe('P2PKH')
expect(regtest.constructor.name).toBe('P2PKH')
expect(regtest.network).toBe(RegTest)
})
})

describe('from() - invalid address', () => {
it('should be able to validate in address prefix with network', () => {
const invalid = DeFiAddress.from('mainnet', p2pkhFixture.invalid)
expect(invalid.valid).toBeFalsy()
})

it('should be able to validate in address prefix with network', () => {
// valid address, used on different network
const p2pkh = DeFiAddress.from('testnet', p2pkhFixture.mainnet)
expect(p2pkh.valid).toBeFalsy()
// expect(p2pkh.type).toBe('P2PKH') // invalid address guessed type is not promising, as p2pkh and p2sh are versy similar
expect(p2pkh.network).toBe(TestNet)
})

it('should get the type precisely', () => {
const invalid = DeFiAddress.from('mainnet', p2pkhFixture.invalidChecksum)
expect(invalid.valid).toBeFalsy()
})
})

describe('to()', () => {
it('should be able to build a new address using a public key hash (20 bytes, 40 char hex string)', () => {
const pubKeyHash = '134b0749882c225e8647df3a3417507c6f5b2797'
expect(pubKeyHash.length).toEqual(40)

const p2pkh = P2PKH.to('regtest', pubKeyHash)
expect(p2pkh.type).toEqual('P2PKH')
expect(p2pkh.valid).toBeTruthy()

const scriptStack = p2pkh.getScript()
expect(scriptStack.stack.length).toEqual(5)
expect(scriptStack.stack[0]).toEqual(OP_CODES.OP_DUP)
expect(scriptStack.stack[1]).toEqual(OP_CODES.OP_HASH160)
expect(scriptStack.stack[2]).toEqual(OP_CODES.OP_PUSHDATA_HEX_LE(pubKeyHash))
expect(scriptStack.stack[3]).toEqual(OP_CODES.OP_EQUALVERIFY)
expect(scriptStack.stack[4]).toEqual(OP_CODES.OP_CHECKSIG)
})

it('should reject invalid data - not 20 bytes data', () => {
const pubKeyHash = '134b0749882c225e8647df3a3417507c6f5b27'
expect(pubKeyHash.length).toEqual(38)

expect(() => {
P2PKH.to('regtest', pubKeyHash)
}).toThrow('InvalidDataLength')
})
})

describe('getScript()', () => {
it('should refuse to build ops code stack for invalid address', () => {
const invalid = DeFiAddress.from('testnet', p2pkhFixture.mainnet)
expect(invalid.valid).toBeFalsy()
try {
invalid.getScript()
} catch (e) {
expect(e.message).toBe('InvalidDefiAddress')
}
})

it('should be able to build script', async () => {
const p2pkh = DeFiAddress.from('mainnet', p2pkhFixture.mainnet)
const scriptStack = p2pkh.getScript()

expect(scriptStack.stack.length).toEqual(5)
expect(scriptStack.stack[0]).toEqual(OP_CODES.OP_DUP)
expect(scriptStack.stack[1]).toEqual(OP_CODES.OP_HASH160)
expect(scriptStack.stack[2].type).toEqual('OP_PUSHDATA') // tested in `to()`
expect(scriptStack.stack[3]).toEqual(OP_CODES.OP_EQUALVERIFY)
expect(scriptStack.stack[4]).toEqual(OP_CODES.OP_CHECKSIG)
})
})

it('validate()', () => {
const hex = bs58.decode(p2pkhFixture.mainnet).toString('hex').substring(2, 42) // take 20 bytes data only
const p2pkh = new P2PKH(MainNet, p2pkhFixture.mainnet, hex)

expect(p2pkh.validatorPassed).toEqual(0)
expect(p2pkh.valid).toBeFalsy()

const isValid = p2pkh.validate()
expect(p2pkh.validatorPassed).toEqual(5)
expect(isValid).toBeTruthy()
})

it('guess()', () => {
const p2pkh = DeFiAddress.guess(p2pkhFixture.mainnet)
expect(p2pkh.valid).toBeTruthy()
expect(p2pkh.type).toBe('P2PKH')
expect(p2pkh.constructor.name).toBe('P2PKH')
expect(p2pkh.network).toBe(MainNet)
})
})
Loading

0 comments on commit 1bfeab9

Please sign in to comment.