Created
April 5, 2021 06:25
-
-
Save fuxingloh/239b76024b32173c2f7e56883d8e23ec to your computer and use it in GitHub Desktop.
Bip32Path TypeScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
}) | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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