Files
xahau.js/packages/ripple-keypairs/src/index.ts
2023-05-17 06:35:54 +00:00

169 lines
4.6 KiB
TypeScript

import * as assert from 'assert'
import brorand = require('brorand')
import * as hashjs from 'hash.js'
import * as elliptic from 'elliptic'
import * as addressCodec from '@transia/ripple-address-codec'
import { derivePrivateKey, accountPublicFromPublicGenerator } from './secp256k1'
import * as utils from './utils'
const Ed25519 = elliptic.eddsa('ed25519')
const Secp256k1 = elliptic.ec('secp256k1')
const { hexToBytes } = utils
const { bytesToHex } = utils
function generateSeed(
options: {
entropy?: Uint8Array
algorithm?: 'ed25519' | 'ecdsa-secp256k1'
} = {},
): string {
assert.ok(
!options.entropy || options.entropy.length >= 16,
'entropy too short',
)
const entropy = options.entropy ? options.entropy.slice(0, 16) : brorand(16)
const type = options.algorithm === 'ed25519' ? 'ed25519' : 'secp256k1'
return addressCodec.encodeSeed(Buffer.from(entropy), type)
}
function hash(message): number[] {
return hashjs.sha512().update(message).digest().slice(0, 32)
}
const secp256k1 = {
deriveKeypair(
entropy: Uint8Array,
options?: object,
): {
privateKey: string
publicKey: string
} {
const prefix = '00'
const privateKey =
prefix + derivePrivateKey(entropy, options).toString(16, 64).toUpperCase()
const publicKey = bytesToHex(
Secp256k1.keyFromPrivate(privateKey.slice(2))
.getPublic()
.encodeCompressed(),
)
return { privateKey, publicKey }
},
sign(message, privateKey): string {
return bytesToHex(
Secp256k1.sign(hash(message), hexToBytes(privateKey), {
canonical: true,
}).toDER(),
)
},
verify(message, signature, publicKey): boolean {
return Secp256k1.verify(hash(message), signature, hexToBytes(publicKey))
},
}
const ed25519 = {
deriveKeypair(entropy: Uint8Array): {
privateKey: string
publicKey: string
} {
const prefix = 'ED'
const rawPrivateKey = hash(entropy)
const privateKey = prefix + bytesToHex(rawPrivateKey)
const publicKey =
prefix + bytesToHex(Ed25519.keyFromSecret(rawPrivateKey).pubBytes())
return { privateKey, publicKey }
},
sign(message, privateKey): string {
// caution: Ed25519.sign interprets all strings as hex, stripping
// any non-hex characters without warning
assert.ok(Array.isArray(message), 'message must be array of octets')
return bytesToHex(
Ed25519.sign(message, hexToBytes(privateKey).slice(1)).toBytes(),
)
},
verify(message, signature, publicKey): boolean {
return Ed25519.verify(
message,
hexToBytes(signature),
hexToBytes(publicKey).slice(1),
)
},
}
function select(algorithm): any {
const methods = { 'ecdsa-secp256k1': secp256k1, ed25519 }
return methods[algorithm]
}
function deriveKeypair(
seed: string,
options?: object,
): {
publicKey: string
privateKey: string
} {
const decoded = addressCodec.decodeSeed(seed)
const algorithm = decoded.type === 'ed25519' ? 'ed25519' : 'ecdsa-secp256k1'
const method = select(algorithm)
const keypair = method.deriveKeypair(decoded.bytes, options)
const messageToVerify = hash('This test message should verify.')
const signature = method.sign(messageToVerify, keypair.privateKey)
/* istanbul ignore if */
if (method.verify(messageToVerify, signature, keypair.publicKey) !== true) {
throw new Error('derived keypair did not generate verifiable signature')
}
return keypair
}
function getAlgorithmFromKey(key): 'ed25519' | 'ecdsa-secp256k1' {
const bytes = hexToBytes(key)
return bytes.length === 33 && bytes[0] === 0xed
? 'ed25519'
: 'ecdsa-secp256k1'
}
function sign(messageHex, privateKey): string {
const algorithm = getAlgorithmFromKey(privateKey)
return select(algorithm).sign(hexToBytes(messageHex), privateKey)
}
function verify(messageHex, signature, publicKey): boolean {
const algorithm = getAlgorithmFromKey(publicKey)
return select(algorithm).verify(hexToBytes(messageHex), signature, publicKey)
}
function deriveAddressFromBytes(publicKeyBytes: Buffer): string {
return addressCodec.encodeAccountID(
utils.computePublicKeyHash(publicKeyBytes),
)
}
function deriveAddress(publicKey): string {
return deriveAddressFromBytes(Buffer.from(hexToBytes(publicKey)))
}
function deriveNodeAddress(publicKey): string {
const generatorBytes = addressCodec.decodeNodePublic(publicKey)
const accountPublicBytes = accountPublicFromPublicGenerator(generatorBytes)
return deriveAddressFromBytes(accountPublicBytes)
}
const { decodeSeed } = addressCodec
export {
generateSeed,
deriveKeypair,
sign,
verify,
deriveAddress,
deriveNodeAddress,
decodeSeed,
}