Skip to content

Instantly share code, notes, and snippets.

@fuxingloh
Created April 5, 2021 06:25
Show Gist options
  • Save fuxingloh/239b76024b32173c2f7e56883d8e23ec to your computer and use it in GitHub Desktop.
Save fuxingloh/239b76024b32173c2f7e56883d8e23ec to your computer and use it in GitHub Desktop.
Bip32Path TypeScript
import { BIP32Path } from '../../src/utils/bip32_path'
describe('fromPathArray()', function () {
it('should work with proper input', function () {
const bipPath = BIP32Path.fromPathArray([44 | 0x80000000, 1, 1, 0])
expect(bipPath.toString()).toBe("m/44'/1/1/0")
})
})
describe('toPathArray()', function () {
it('should work with proper input', function () {
const bipPath = BIP32Path.fromPathArray([44 | 0x80000000, 1, 1, 0])
expect(bipPath.toPathArray()).toStrictEqual([44 | 0x80000000, 1, 1, 0])
})
})
describe('fromString()', function () {
it('should work with new style input', function () {
expect(BIP32Path.fromString('m/44\'/0\'/0\'').toString()).toBe('m/44\'/0\'/0\'')
})
it('should work without m/ prefix', function () {
expect(BIP32Path.fromString('44\'/0\'/0\'').toString()).toBe('m/44\'/0\'/0\'')
})
it('should require the m/ prefix', function () {
expect(BIP32Path.fromString('m/44\'/0\'/0\'', true).toString()).toBe('m/44\'/0\'/0\'')
})
it('should require the m/ prefix (and fail)', async () => {
await expect(() => {
BIP32Path.fromString('44\'/0\'/0\'', true)
}).toThrow()
})
it('should not work with invalid index', async () => {
await expect(() => {
BIP32Path.fromString('44\'/2147483648')
})
await expect(() => {
BIP32Path.fromString('44\'/2147483648\'')
})
})
it('should work with large indexes', function () {
const bipPath = BIP32Path.fromString('m/0/2147483647\'/1/2147483646\'/2')
expect(bipPath.toString()).toBe('m/0/2147483647\'/1/2147483646\'/2')
})
it('should not return negative indexes', function () {
const bipPath = BIP32Path.fromString('m/44\'/0\'/0\'')
expect(bipPath.toPathArray()[0]).toBe(2147483692)
})
})
describe('toString()', function () {
it('should work with new style output', function () {
const bipPath = BIP32Path.fromPathArray([44 | 0x80000000, 1, 1, 0])
expect(bipPath.toString()).toBe("m/44'/1/1/0")
})
it('should work with new style output (without m/ prefix)', function () {
const bipPath = BIP32Path.fromPathArray([44 | 0x80000000, 1, 1, 0])
expect(bipPath.toString(false)).toBe("44'/1/1/0")
})
})
const HARDENED = 0x80000000
/**
* Based on https://github.com/axic/bip32-path
*
* The MIT License (MIT)
* Copyright (c) 2016 Alex Beregszaszi
*/
export class BIP32Path {
private readonly path: number[]
private constructor (path: number[]) {
if (!Array.isArray(path)) {
throw new Error('Input must be an Array')
}
if (path.length === 0) {
throw new Error('Path must contain at least one level')
}
for (let i = 0; i < path.length; i++) {
if (typeof path[i] !== 'number') {
throw new Error('Path element is not a number')
}
}
this.path = path
}
static fromPathArray (path: number[]): BIP32Path {
return new BIP32Path(path)
}
/**
* @param text to create BipPath from
* @param reqRoot whether root 'm' is required
*
* @example of support text
* 0/0/0
* m/0/0
* m/0'/0'
* m/0'/0'/0
*/
static fromString (text: string, reqRoot: boolean = false): BIP32Path {
// skip the root
if (/^m\//i.test(text)) {
text = text.slice(2)
} else if (reqRoot) {
throw new Error('Root element is required')
}
const splits = text.split('/')
const paths: number[] = new Array(splits.length)
for (let i = 0; i < splits.length; i++) {
const tmp = /(\d+)([hH']?)/.exec(splits[i])
if (tmp === null) {
throw new Error('Invalid input')
}
paths[i] = parseInt(tmp[1], 10)
if (paths[i] >= HARDENED) {
throw new Error('Invalid child index')
}
if (tmp[2] === "'") {
paths[i] += HARDENED
} else if (tmp[2].length !== 0) {
throw new Error('Invalid modifier')
}
}
return new BIP32Path(paths)
}
/**
* @return as number[] array
*/
toPathArray (): number[] {
return this.path
}
/**
* @param root whether to include root
*/
toString (root: boolean = true): string {
const ret: string[] = new Array(this.path.length)
for (let i = 0; i < this.path.length; i++) {
const tmp = this.path[i]
if ((tmp & HARDENED) !== 0) {
ret[i] = `${tmp & ~HARDENED}'`
} else {
ret[i] = `${tmp}`
}
}
return (root ? 'm/' : '') + ret.join('/')
}
/**
* Bip32 path as buffer.
* @return [
* length of derivations,
* path0,
* path1,
* path2,
* ...
* ]
*/
asBuffer (): Buffer {
const paths = this.toPathArray()
const buffer = Buffer.alloc(1 + paths.length * 4)
buffer[0] = paths.length
paths.forEach((element, index) => {
buffer.writeUInt32BE(element, 1 + 4 * index)
})
return buffer
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment