Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

guess P2SH / P2PKH / P2WSH / P2WPKH type #189

Merged
merged 38 commits into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
42711e3
added few Address class type and a static guess()
May 6, 2021
85593cd
quick save
May 10, 2021
7b4d10f
quick save
May 10, 2021
b0e9c21
remove invalid package dep
May 10, 2021
95b6720
remove unused stuff
May 10, 2021
e3adb4e
delete address validator in jellyfish-tx packageg
May 10, 2021
89877c6
minor rename function
May 10, 2021
71ec388
add jlf-tx to jlf-add dependency
May 10, 2021
db72660
add name field into network interface, ease reading and comparing
May 11, 2021
f66ae3f
merge base
May 11, 2021
4db7fac
enclose few "guess" logic with try catch prevent undesired error thro…
May 11, 2021
6446bc8
add some jsdoc
May 11, 2021
d800880
remove unused arg
May 11, 2021
247fa7c
add stop container to test
May 11, 2021
79fbda2
revert changes done by prettier
May 12, 2021
e8f706a
fix accidentally remove intro md
May 12, 2021
a727a8b
remove unused file
May 12, 2021
b1b5531
remove unused file
May 12, 2021
9ffb47a
move bs58 dep to jlf-add package
May 12, 2021
2c784a1
remove debug log
May 12, 2021
7325ea9
ignore lerna debug log in git
May 12, 2021
9041477
revert prettier formatting
May 12, 2021
379a50d
refactored address lib into smaller file by category, by type
May 12, 2021
10ae62b
added bech32 address test
May 12, 2021
b5e4192
resolve conflict, duplicated identifier
May 12, 2021
757c35c
minor lint fix, uncomment a test
May 12, 2021
10b8476
revert JSON no endline auto fix
May 12, 2021
4aa397c
remove jellyfish address default export
May 12, 2021
dd5c392
fix comment (clean up git history)
May 12, 2021
20718ee
clean git history (jlf network)
May 12, 2021
1391ad6
Merge branch 'main' into feature/wallet-address-validator
May 17, 2021
6b7a806
simply h160 to bs58 address construction, must specify address type (…
May 17, 2021
cdc85c6
fix test, b58 address no longer hold full hex, but h160 only in hex f…
May 17, 2021
9e78608
jellyfish-address refactor (#238)
fuxingloh May 17, 2021
8e1ebab
fix test, b58 address no longer hold full hex, but h160 only in hex f…
May 17, 2021
4721a64
jellyfish-address refactor (#238)
fuxingloh May 17, 2021
c47ebe9
Merge remote-tracking branch 'origin/feature/wallet-address-validator…
fuxingloh May 17, 2021
b82f137
Merge remote-tracking branch 'origin/main' into feature/wallet-addres…
fuxingloh May 17, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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