mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 04:05:52 +00:00
169 lines
4.6 KiB
TypeScript
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,
|
|
}
|