diff --git a/packages/util-crypto/src/hd/ledger/derivePrivate.ts b/packages/util-crypto/src/hd/ledger/derivePrivate.ts index c738e7dac..fa6236741 100644 --- a/packages/util-crypto/src/hd/ledger/derivePrivate.ts +++ b/packages/util-crypto/src/hd/ledger/derivePrivate.ts @@ -1,34 +1,29 @@ // Copyright 2017-2021 @polkadot/util-crypto authors & contributors // SPDX-License-Identifier: Apache-2.0 -import BN from 'bn.js'; - -import { bnToU8a, u8aConcat } from '@polkadot/util'; +import { bnToU8a, u8aConcat, u8aToBn } from '@polkadot/util'; import { hmacSha512 } from '../../hmac'; // performs hard-only derivation on the xprv export function ledgerDerivePrivate (xprv: Uint8Array, index: number): Uint8Array { - const kl = xprv.slice(0, 32); - const kr = xprv.slice(32, 64); - const cc = xprv.slice(64, 96); - - const data = new Uint8Array(1 + 64 + 4); - - data.set(bnToU8a(index, { bitLength: 32, isLe: true }), 1 + 64); - data.set(kl, 1); - data.set(kr, 1 + 32); - data[0] = 0x00; - + const kl = xprv.subarray(0, 32); + const kr = xprv.subarray(32, 64); + const cc = xprv.subarray(64, 96); + const data = u8aConcat([0], kl, kr, bnToU8a(index, { bitLength: 32, isLe: true })); const z = hmacSha512(cc, data); data[0] = 0x01; - const chainCode = hmacSha512(cc, data).slice(32, 64); - const zl = z.slice(0, 32); - const zr = z.slice(32, 64); - const left = bnToU8a(new BN(kl, 16, 'le').add(new BN(zl.slice(0, 28), 16, 'le').mul(new BN(8))), { bitLength: 512, isLe: true }).slice(0, 32); - const right = bnToU8a(new BN(kr, 16, 'le').add(new BN(zr, 16, 'le')), { bitLength: 512, isLe: true }).slice(0, 32); - - return u8aConcat(left, right, chainCode); + return u8aConcat( + bnToU8a( + u8aToBn(kl, { isLe: true }).iadd(u8aToBn(z.subarray(0, 28), { isLe: true }).muln(8)), + { bitLength: 512, isLe: true } + ).subarray(0, 32), + bnToU8a( + u8aToBn(kr, { isLe: true }).iadd(u8aToBn(z.subarray(32, 64), { isLe: true })), + { bitLength: 512, isLe: true } + ).subarray(0, 32), + hmacSha512(cc, data).subarray(32, 64) + ); } diff --git a/packages/util-crypto/src/hd/ledger/index.spec.ts b/packages/util-crypto/src/hd/ledger/index.spec.ts index 16da2947a..a21f878db 100644 --- a/packages/util-crypto/src/hd/ledger/index.spec.ts +++ b/packages/util-crypto/src/hd/ledger/index.spec.ts @@ -5,20 +5,52 @@ import { u8aToHex } from '@polkadot/util'; import { hdLedger } from '..'; -describe('ledgerDerive', (): void => { - it('derives a known private key for Kusama', (): void => { - expect(u8aToHex( - hdLedger('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', `m/44'/${0x01b2}'/0'/0'/0'`) - .secretKey - .slice(0, 32) - )).toEqual('0x98cb4e14e0e08ea876f88d728545ea7572dc07dbbe69f1731c418fb827e69d41'); - }); +const MNE_0 = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; +const MNE_1 = 'open jelly jeans corn ketchup supreme brief element armed lens vault weather original scissors rug priority vicious lesson raven spot gossip powder person volcano'; - it('derives a known private key for Polkadot', (): void => { - expect(u8aToHex( - hdLedger('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', `m/44'/${0x80000162}/0'/0'/0'`) - .secretKey - .slice(0, 32) - )).toEqual('0xe8c68348586d53e4e8d1a864b0e4e17c75e4eb06e0c63c1432bef2ba29e69d41'); +const TESTS = { + Kusama: { + slip44: 0x01b2, + tests: [ + { + ed25519: '0x98cb4e14e0e08ea876f88d728545ea7572dc07dbbe69f1731c418fb827e69d41', + index: [0, 0], + mnemonic: MNE_0 + }, + { + ed25519: '0x70e9010e84c81095aaa5f63b1c5a6a66a1dcbec017a23c2f3b7a1b08fe5ea65a', + index: [0, 0], + mnemonic: MNE_1 + }, + { + ed25519: '0xf06730efb1e6ea59ac752a7c3620fade3909062fb88597856cc3af72045fa65a', + index: [5, 7], + mnemonic: MNE_1 + } + ] + }, + Polkadot: { + slip44: 0x80000162, + tests: [ + { + ed25519: '0xe8c68348586d53e4e8d1a864b0e4e17c75e4eb06e0c63c1432bef2ba29e69d41', + index: [0, 0], + mnemonic: MNE_0 + } + ] + } +}; + +describe('ledgerDerive', (): void => { + Object.entries(TESTS).forEach(([network, { slip44, tests }]): void => { + tests.forEach(({ ed25519, index: [account, address], mnemonic }, index): void => { + it(`derives a known ed25519 seed for ${network} (${index})`, (): void => { + expect(u8aToHex( + hdLedger(mnemonic, `m/44'/${slip44}'/${account}'/0'/${address}'`) + .secretKey + .slice(0, 32) + )).toEqual(ed25519); + }); + }); }); });