Skip to content

Commit

Permalink
feat(utils): add blake160, bech32, blake160PubkeyToAddress, pubkeyToA…
Browse files Browse the repository at this point in the history
…ddress
  • Loading branch information
Keith-CY committed Apr 22, 2019
1 parent 9eeb640 commit 79cb24f
Show file tree
Hide file tree
Showing 9 changed files with 496 additions and 12 deletions.
138 changes: 138 additions & 0 deletions packages/ckb-sdk-utils/__tests__/bech32-fixtures.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
{
"fromWords": {
"invalid": [{
"exception": "Excess padding",
"words": [14, 20, 15, 7, 13, 26, 0, 25, 18, 6, 11, 13, 8, 21, 4, 20, 3, 17, 2, 29, 3, 0]
},
{
"exception": "Non-zero padding",
"words": [3, 1, 17, 17, 8, 15, 0, 20, 24, 20, 11, 6, 16, 1, 5, 29, 3, 4, 16, 3, 6, 21, 22, 26, 2, 13, 22, 9, 16, 21, 19, 24, 25, 21, 6, 18, 15, 8, 13, 24, 24, 24, 25, 9, 12, 1, 4, 16, 6, 9, 17, 1]
}
]
},
"bech32": {
"valid": [{
"string": "A12UEL5L",
"prefix": "A",
"hex": "",
"words": []
},
{
"string": "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
"prefix": "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio",
"hex": "",
"words": []
},
{
"string": "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
"prefix": "abcdef",
"hex": "00443214c74254b635cf84653a56d7c675be77df",
"words": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
},
{
"string": "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
"prefix": "1",
"hex": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"words": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
{
"string": "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
"prefix": "split",
"hex": "c5f38b70305f519bf66d85fb6cf03058f3dde463ecd7918f2dc743918f2d",
"words": [24, 23, 25, 24, 22, 28, 1, 16, 11, 29, 8, 25, 23, 29, 19, 13, 16, 23, 29, 22, 25, 28, 1, 16, 11, 3, 25, 29, 27, 25, 3, 3, 29, 19, 11, 25, 3, 3, 25, 13, 24, 29, 1, 25, 3, 3, 25, 13]
},
{
"string": "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq978ear",
"prefix": "1",
"hex": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"words": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"limit": 300
},
{
"string": "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
"prefix": "bc",
"words": [0, 14, 20, 15, 7, 13, 26, 0, 25, 18, 6, 11, 13, 8, 21, 4, 20, 3, 17, 2, 29, 3, 12, 29, 3, 4, 15, 24, 20, 6, 14, 30, 22],
"limit": 300
}
],
"invalid": [{
"string": "A12Uel5l",
"exception": "Mixed-case string A12Uel5l"
},
{
"string": " 1nwldj5",
"exception": "Invalid prefix \\( \\)"
},
{
"string": "abc1rzg",
"exception": "abc1rzg too short"
},
{
"string": "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx",
"exception": "Exceeds length limit"
},
{
"string": "x1b4n0q5v",
"exception": "Unknown character b"
},
{
"string": "1pzry9x0s0muk",
"exception": "Missing prefix for 1pzry9x0s0muk"
},
{
"string": "pzry9x0s0muk",
"exception": "No separator character for pzry9x0s0muk"
},
{
"string": "1pzry9x0s0muk",
"exception": "Missing prefix for 1pzry9x0s0muk"
},
{
"string": "abc1rzgt4",
"exception": "Data too short"
},
{
"string": "s1vcsyn",
"exception": "s1vcsyn too short"
},
{
"prefix": "abc",
"words": [128],
"exception": "Non 5-bit word"
},
{
"prefix": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzfoobarfoobar",
"words": [128],
"exception": "Exceeds length limit"
},
{
"prefix": "foobar",
"words": [20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20],
"exception": "Exceeds length limit"
},
{
"prefix": "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzfoobarfoobarfoobarfoobar",
"words": [128],
"limit": 104,
"exception": "Exceeds length limit"
},
{
"string": "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
"exception": "Exceeds length limit"
},
{
"prefix": "abc\u00ff",
"words": [18],
"exception": "Invalid prefix \\(abc\u00ff\\)"
},
{
"string": "li1dgmt3",
"exception": "Data too short"
},
{
"stringHex": "6465316c67377774ff",
"exception": "Unknown character "
}
]
}
}
112 changes: 107 additions & 5 deletions packages/ckb-sdk-utils/__tests__/ckb-utils.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
const ckbUtils = require('../lib')
const bech32Fixtures = require('./bech32-fixtures.json')

describe('blake2b', () => {
const {
blake2b,
blake160,
bech32,
blake160PubkeyToAddress,
pubkeyToAddress,
hexToBytes,
bytesToHex,
lockScriptToHash,
PERSONAL,
} = ckbUtils

describe('format', () => {
it('hex to bytes', () => {
const fixture = {
hex: 'abcd12',
bytes: [171, 205, 18],
}
const bytes = hexToBytes(fixture.hex)
expect(bytes.join(',')).toBe(fixture.bytes.join(','))
})

it('bytes to hex', () => {
const fixture = {
bytes: [171, 205, 18],
hex: 'abcd12',
}
const hex = bytesToHex(fixture.bytes)
expect(hex).toBe(fixture.hex)
})
})

describe('blake', () => {
it('blake2b("") with personal', () => {
const fixture = {
str: '',
digest: '44f4c69744d5f8c55d642062949dcae49bc4e7ef43d388c5a12f42b5633d163e',
}
const s = ckbUtils.blake2b(32, null, null, ckbUtils.PERSONAL)
const s = blake2b(32, null, null, PERSONAL)
s.update(Buffer.from(fixture.str, 'utf8'))
const digest = s.digest('hex')
expect(digest).toBe(fixture.digest)
Expand All @@ -17,14 +50,57 @@ describe('blake2b', () => {
str: 'The quick brown fox jumps over the lazy dog',
digest: 'abfa2c08d62f6f567d088d6ba41d3bbbb9a45c241a8e3789ef39700060b5cee2',
}
const s = ckbUtils.blake2b(32, null, null, ckbUtils.PERSONAL)
const s = blake2b(32, null, null, PERSONAL)
s.update(Buffer.from(fixture.str, 'utf8'))
const digest = s.digest('hex')
expect(digest).toBe(fixture.digest)
})

it('blake160', () => {
const fixture = {
str: '024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01',
digest: '36c329ed630d6ce750712a477543672adab57f4c',
}
const digest = blake160(Buffer.from(fixture.str, 'hex'), 'hex')
expect(digest).toBe(fixture.digest)
})
})

describe('bech32', () => {
bech32Fixtures.bech32.valid.forEach(f => {
it(`fromWords/toWords ${f.hex}`, () => {
if (f.hex) {
const words = bech32.toWords(Buffer.from(f.hex, 'hex'))
const bytes = Buffer.from(bech32.fromWords(f.words))
expect(words.join('')).toEqual(f.words.join(''))
expect(bytes.toString('hex')).toBe(f.hex)
}
})

it(`encode ${f.prefix}`, () => {
const encoded = bech32.encode(f.prefix, f.words, f.limit)
expect(encoded).toBe(f.string.toLowerCase())
})

it(`decode ${f.string}`, () => {
const decoded = bech32.decode(f.string, f.limit)
expect(decoded.prefix).toBe(f.prefix.toLowerCase())
expect(decoded.words.join('')).toBe(f.words.join(''))
})

it(`fails for ${f.string} with 1 bit flipped`, () => {
const buf = Buffer.from(f.string, 'utf8')
buf[f.string.lastIndexOf('1') + 1] ^= 0x1
const str = buf.toString('utf8')
expect(() => {
bech32.decode(str, f.limit)
}).toThrow()
})
})
})

describe('scriptToHash', () => {
it('scriptToHash(basic script', () => {
it('scriptToHash(basic script)', () => {
const fixture = {
script: {
version: 0,
Expand All @@ -33,7 +109,33 @@ describe('scriptToHash', () => {
},
lockHash: 'dade0e507e27e2a5995cf39c8cf454b6e70fa80d03c1187db7a4cb2c9eab79da',
}
const lockHash = ckbUtils.lockScriptToHash(fixture.script)
const lockHash = lockScriptToHash(fixture.script)
expect(lockHash).toBe(fixture.lockHash)
})
})

describe('address', () => {
it('pubkey blake160 to address', () => {
const fixture = {
str: '36c329ed630d6ce750712a477543672adab57f4c',
prefix: 'ckt',
address: 'ckt1q9gry5zgxmpjnmtrp4kww5r39frh2sm89tdt2l6v234ygf',
}
const address = blake160PubkeyToAddress(fixture.str, {
prefix: fixture.prefix,
})
expect(address).toBe(fixture.address)
})

it('pubkey to address', () => {
const fixture = {
pubkey: '024a501efd328e062c8675f2365970728c859c592beeefd6be8ead3d901330bc01',
prefix: 'ckt',
address: 'ckt1q9gry5zgxmpjnmtrp4kww5r39frh2sm89tdt2l6v234ygf',
}
const address = pubkeyToAddress(hexToBytes(fixture.pubkey), {
prefix: fixture.prefix,
})
expect(address).toBe(fixture.address)
})
})
1 change: 0 additions & 1 deletion packages/ckb-sdk-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
},
"dependencies": {
"@nervosnetwork/ckb-types": "^0.9.0",
"bech32": "^1.1.3",
"blake2b-wasm": "^1.1.7",
"bs58check": "^2.1.2",
"eth-lib": "^0.2.8",
Expand Down
75 changes: 75 additions & 0 deletions packages/ckb-sdk-utils/src/address/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { bech32, blake160, hexToBytes } from '..'

export enum AddressPrefix {
Mainnet = 'ckb',
Testnet = 'ckt',
}

export enum AddressType {
BinHash = '0x00',
BinIdx = '0x01',
}

export enum AddressBinIdx {
P2PH = 'P2PH',
}

export interface AddressOptions {
prefix: AddressPrefix
type: AddressType
binIdx: AddressBinIdx
}

export const defaultAddressOptions = {
prefix: AddressPrefix.Mainnet,
type: AddressType.BinIdx,
binIdx: AddressBinIdx.P2PH,
}

/**
* @description payload = type(01) | bin-idx("P2PH" => "50|32|50|40") | blake160-formatted pubkey
* @see https://github.com/nervosnetwork/ckb/wiki/Common-Address-Format
*/
export const toAddressPayload = (
blake160Pubkey: string | Uint8Array,
type: AddressType = AddressType.BinIdx,
params: AddressBinIdx = AddressBinIdx.P2PH
): Uint8Array => {
if (typeof blake160Pubkey === 'string') {
return new Uint8Array([...hexToBytes(type), ...Buffer.from(params), ...hexToBytes(blake160Pubkey)])
}
return new Uint8Array([...hexToBytes(type), ...Buffer.from(params), ...blake160Pubkey])
}

export const blake160PubkeyToAddress = (
blake160Pubkey: Uint8Array | string,
{
prefix = AddressPrefix.Mainnet,
type = AddressType.BinIdx,
binIdx = AddressBinIdx.P2PH,
}: AddressOptions = defaultAddressOptions
) => bech32.encode(prefix, bech32.toWords(toAddressPayload(blake160Pubkey, type, binIdx)))

export const pubkeyToAddress = (
pubkey: Uint8Array | string,
{
prefix = AddressPrefix.Mainnet,
type = AddressType.BinIdx,
binIdx = AddressBinIdx.P2PH,
}: AddressOptions = defaultAddressOptions
) => {
const blake160Pubkey = blake160(pubkey)
return blake160PubkeyToAddress(blake160Pubkey, {
prefix,
type,
binIdx,
})
}

export const parseAddress = (address: string, prefix: AddressPrefix = AddressPrefix.Mainnet) => {
const decoded = bech32.decode(address)
if (decoded.prefix !== prefix) {
throw new Error('Prefix not matched')
}
return decoded.words
}
5 changes: 0 additions & 5 deletions packages/ckb-sdk-utils/src/crypto.ts

This file was deleted.

Loading

0 comments on commit 79cb24f

Please sign in to comment.