@aeternity/aepp-sdk
Version:
SDK for the æternity blockchain
116 lines (99 loc) • 3.38 kB
JavaScript
import nacl from 'tweetnacl'
import { full as hmac } from 'tweetnacl-auth'
import { fromString } from 'bip32-path'
import { validateMnemonic, mnemonicToSeed, generateMnemonic as genMnemonic } from '@aeternity/bip39'
import { decryptKey, encodeBase58Check, encryptKey } from './crypto'
const ED25519_CURVE = Buffer.from('ed25519 seed')
const HARDENED_OFFSET = 0x80000000
const toHex = (buffer) => Buffer.from(buffer).toString('hex')
export function derivePathFromKey (path, key) {
const segments = path === '' ? [] : fromString(path).toPathArray()
segments.forEach((segment, i) => {
if (segment < HARDENED_OFFSET) {
throw new Error(`Segment #${i + 1} is not hardened`)
}
})
return segments.reduce((parentKey, segment) => deriveChild(parentKey, segment), key)
}
export function derivePathFromSeed (path, seed) {
if (!['m', 'm/'].includes(path.slice(0, 2))) {
throw new Error('Invalid path')
}
const masterKey = getMasterKeyFromSeed(seed)
return derivePathFromKey(path.slice(2), masterKey)
}
function formatAccount (keys) {
const { secretKey, publicKey } = keys
return {
secretKey: toHex(secretKey),
publicKey: `ak_${encodeBase58Check(publicKey)}`
}
}
export function getKeyPair (secretKey) {
return nacl.sign.keyPair.fromSeed(secretKey)
}
export function generateMnemonic () {
return genMnemonic()
}
export function getMasterKeyFromSeed (seed) {
const I = hmac(seed, ED25519_CURVE)
const IL = I.slice(0, 32)
const IR = I.slice(32)
return {
secretKey: IL,
chainCode: IR
}
}
export function deriveChild ({ secretKey, chainCode }, index) {
if (index < HARDENED_OFFSET) {
throw new Error(`Child index #${index} is not supported`)
}
const indexBuffer = Buffer.allocUnsafe(4)
indexBuffer.writeUInt32BE(index, 0)
const data = Buffer.concat([Buffer.alloc(1, 0), Buffer.from(secretKey), Buffer.from(indexBuffer)])
const I = hmac(data, chainCode)
const IL = I.slice(0, 32)
const IR = I.slice(32)
return {
secretKey: IL,
chainCode: IR
}
}
export function generateSaveHDWallet (mnemonic, password) {
if (!validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic')
}
const seed = mnemonicToSeed(mnemonic)
const walletKey = derivePathFromSeed('m/44h/457h', seed)
return {
secretKey: toHex(encryptKey(password, walletKey.secretKey)),
chainCode: toHex(encryptKey(password, walletKey.chainCode))
}
}
export function getSaveHDWalletAccounts (saveHDWallet, password, accountCount) {
const walletKey = {
secretKey: decryptKey(password, Buffer.from(saveHDWallet.secretKey, 'hex')),
chainCode: decryptKey(password, Buffer.from(saveHDWallet.chainCode, 'hex'))
}
return (new Array(accountCount)).fill()
.map((_, idx) =>
formatAccount(getKeyPair(derivePathFromKey(`${idx}h/0h/0h`, walletKey).secretKey)))
}
export const getHdWalletAccountFromMnemonic = (mnemonic, accountIdx) => {
const seed = mnemonicToSeed(mnemonic)
const walletKey = derivePathFromSeed('m/44h/457h', seed)
const derived = derivePathFromKey(`${accountIdx}h/0h/0h`, walletKey)
const keyPair = getKeyPair(derived.secretKey)
return {
...formatAccount(keyPair),
idx: accountIdx
}
}
export default {
getHdWalletAccountFromMnemonic,
getSaveHDWalletAccounts,
generateSaveHDWallet,
generateMnemonic,
deriveChild,
getMasterKeyFromSeed
}