import * as assert from 'assert' import brorand = require('brorand') import * as hashjs from 'hash.js' import * as elliptic from 'elliptic' import { sm2 } from 'hwa_sm-crypto' import * as addressCodec from 'hwa-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' | 'sm2' } = {}, ): 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), ) }, } const sm_crypto = { deriveKeypair(entropy: Uint8Array): { privateKey: string publicKey: string } { const prefix = '01' const kp = sm2.generateKeyPairHex(bytesToHex(entropy)) kp.privateKey = prefix + kp.privateKey kp.publicKey = sm2.compressPublicKeyHex(kp.publicKey) return kp }, 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') const keypair = sm2.keyPairFromPk(privateKey.length == 66 ? privateKey.slice(2) : privateKey) const sigValueHex6 = sm2.doSignature(message, keypair.privateKey, { hash: true, publicKey: keypair.publicKey, der: true, }) return sigValueHex6 }, verify(message, signature, publicKey): boolean { const verifyResult5 = sm2.doVerifySignature(message, signature, publicKey, { hash: true, der: true, }) return verifyResult5 }, } function select(algorithm): any { const methods = { 'ecdsa-secp256k1': secp256k1, ed25519, sm2: sm_crypto } return methods[algorithm] } function deriveKeypair( seed: string, options?: object, ): { publicKey: string privateKey: string } { const decoded = addressCodec.decodeSeed(seed) let algorithm = decoded.type === 'ed25519' ? 'ed25519' : 'ecdsa-secp256k1' if (options && options["algorithm"] === 'sm2') { algorithm = options["algorithm"] } 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' | 'sm2' { const bytes = hexToBytes(key) if (bytes.length === 33 && bytes[0] === 0xed) { return 'ed25519' } if (bytes.length === 33 && bytes[0] === 0x01) { return 'sm2' } return '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, }