* @param {number} account - Account number between 0 and 2^31-1
* @returns {Promise<ArrayBuffer>} Private child key for the account
*/
- static ckd (seed: ArrayBuffer, coin: number, account: number, chain?: number, address?: number): Promise<ArrayBuffer | undefined> {
+ static ckd (seed: ArrayBuffer, coin: number, account: number, change?: number, address?: number): Promise<ArrayBuffer> {
if (seed.byteLength < 16 || seed.byteLength > 64) {
throw new RangeError(`Invalid seed length`)
}
+ if (!Number.isSafeInteger(coin) || coin < 0 || coin > 0x7fffffff) {
+ throw new RangeError(`Invalid coin 0x${coin.toString(16)}`)
+ }
if (!Number.isSafeInteger(account) || account < 0 || account > 0x7fffffff) {
- throw new RangeError(`Invalid child key index 0x${account.toString(16)}`)
+ throw new RangeError(`Invalid account index 0x${account.toString(16)}`)
+ }
+ if (change != null && (!Number.isSafeInteger(change) || change < 0 || change > 1)) {
+ throw new RangeError(`Invalid change index 0x${account.toString(16)}`)
}
- return this.slip10(SLIP10_ED25519, seed)
+ if (address != null && (!Number.isSafeInteger(address) || address < 0 || address > 0x7fffffff)) {
+ throw new RangeError(`Invalid address index 0x${account.toString(16)}`)
+ }
+ return this.#slip10(SLIP10_ED25519, seed)
.then(masterKey => {
- return this.CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET)
+ return this.#CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET)
.then(purposeKey => {
- return this.CKDpriv(purposeKey, coin + HARDENED_OFFSET)
+ return this.#CKDpriv(purposeKey, coin + HARDENED_OFFSET)
.then(coinKey => {
- return this.CKDpriv(coinKey, account + HARDENED_OFFSET)
+ return this.#CKDpriv(coinKey, account + HARDENED_OFFSET)
.then(accountKey => {
- if (chain == null) return accountKey.privateKey
- this.CKDpriv(accountKey, chain + HARDENED_OFFSET)
+ if (change == null) return accountKey.privateKey
+ return this.#CKDpriv(accountKey, change + HARDENED_OFFSET)
.then(chainKey => {
if (address == null) return chainKey.privateKey
- this.CKDpriv(chainKey, address + HARDENED_OFFSET)
+ return this.#CKDpriv(chainKey, address + HARDENED_OFFSET)
.then(addressKey => {
return addressKey.privateKey
})
})
})
})
- .catch(err => {
- throw new Error('Failed to derive BIP-44 account', { cause: err })
- })
}
- static slip10 (curve: string, S: ArrayBuffer): Promise<ExtendedKey> {
+ static #slip10 (curve: string, S: ArrayBuffer): Promise<ExtendedKey> {
const key = new Uint8Array(new TextEncoder().encode(curve))
const data = new Uint8Array(S)
- return this.hmac(key, data)
+ return this.#hmac(key, data)
.then(I => {
const IL = I.slice(0, I.byteLength / 2)
const IR = I.slice(I.byteLength / 2)
})
}
- static CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {
+ static #CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {
const key = new Uint8Array(chainCode)
const data = new Uint8Array(37)
data.set([0])
- data.set(this.ser256(privateKey), 1)
- data.set(this.ser32(index), 33)
- return this.hmac(key, data)
+ data.set(this.#ser256(privateKey), 1)
+ data.set(this.#ser32(index), 33)
+ return this.#hmac(key, data)
.then(I => {
const IL = I.slice(0, I.byteLength / 2)
const IR = I.slice(I.byteLength / 2)
})
}
- static ser32 (integer: number): Uint8Array<ArrayBuffer> {
- if (typeof integer !== 'number') {
- throw new TypeError(`Expected a number, received ${typeof integer}`)
- }
- if (integer > 0xffffffff) {
- throw new RangeError(`Expected 32-bit integer, received ${integer.toString(2).length}-bit value: ${integer}`)
- }
+ static #ser32 (integer: number): Uint8Array<ArrayBuffer> {
const view = new DataView(new ArrayBuffer(4))
view.setUint32(0, integer, false)
return new Uint8Array(view.buffer)
}
- static ser256 (integer: ArrayBuffer): Uint8Array<ArrayBuffer> {
- if (!(integer instanceof ArrayBuffer)) {
- throw new TypeError(`Expected ArrayBuffer, received ${typeof integer}`)
- }
- if (integer.byteLength > 32) {
- throw new RangeError(`Expected 32-byte integer, received ${integer.byteLength}-byte value: ${integer}`)
- }
+ static #ser256 (integer: ArrayBuffer): Uint8Array<ArrayBuffer> {
return new Uint8Array(integer)
}
- static hmac (key: Uint8Array<ArrayBuffer>, data: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer> {
+ static #hmac (key: Uint8Array<ArrayBuffer>, data: Uint8Array<ArrayBuffer>): Promise<ArrayBuffer> {
return crypto.subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign'])
.then(pk => {
return crypto.subtle.sign('HMAC', pk, data)