twetch-sdk 0.0.1

rust twetch sdk with wasm builds
Documentation
import { PrivateKey, PublicKey, ECIES, Script } from '../../../pkg/node'
import { assert, util } from 'chai'
import Aes from 'aes-js'

import bsv from 'bsv'

function aesCBCEncrypt(plainText, kE, iV) {
	const plainBytes = Aes.padding.pkcs7.pad(Aes.utils.hex.toBytes(plainText))
	const aes = new Aes.ModeOfOperation.cbc(kE, iV)
	const encryptedBytes = aes.encrypt(plainBytes)
	const encryptedHex = Aes.utils.hex.fromBytes(encryptedBytes)
	return encryptedHex
}

function aesCBCDecrypt(encryptedHex, kE, iV) {
	const encryptedBytes = Aes.utils.hex.toBytes(encryptedHex).slice(37, -32)
	const aes = new Aes.ModeOfOperation.cbc(kE, iV)
	const plainBytes = Aes.padding.pkcs7.strip(aes.decrypt(encryptedBytes))
	const plainText = Aes.utils.hex.fromBytes(plainBytes)
	return plainText
}

function eciesEphemeralEncrypt(plainText, publicKey, r) {
	const rN = r.bn
	const k = bsv.PublicKey(publicKey).point
	const P = k.mul(rN)
	const hash = bsv.crypto.Hash.sha512(bsv.PublicKey(P).toBuffer())
	const iV = hash.slice(0, 16)
	const kE = hash.slice(16, 32)
	const kM = hash.slice(32, 64)
	const encryptedText = aesCBCEncrypt(plainText, kE, iV)
	const encryptedBytes = Buffer.from(encryptedText, 'hex')
	const msgBuf = Buffer.concat([Buffer.from('BIE1'), r.publicKey.toDER(true), encryptedBytes])
	const hmac = bsv.crypto.Hash.sha256hmac(msgBuf, kM)
	return {
		cipherText_js: Buffer.concat([msgBuf, hmac]).toString('hex'),
		hash_js: hash.toString('hex')
	}
}

function eciesEphemeralDecrypt(encryptedHex, hash) {
	const buf = Buffer.from(hash, 'hex')
	const iV = buf.slice(0, 16)
	const kE = buf.slice(16, 32)

	return aesCBCDecrypt(encryptedHex, kE, iV)
}

describe('encryption', () => {
	it('eciesEphemeralEncrypt', () => {
		//const args = [
		//'1LoveF7qQijpjascPytHor2uSEEjHHH8YB',
		//'1447f87d79e395f75e3cd5ef7edf822fbf4aaf1c2e8b22f1cf8791575bb756bf',
		//'twetch',
		//'8e48e49e-3332-4a52-b69d-016ecd006b0a',
		//'|',
		//'15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva',
		//'BITCOIN_ECDSA',
		//'12tDncQvFZaZzqanupmtXpDUm42Wd4Cn4W',
		//'IMCdzg9LJfrDNJdFPB1qhd5nCIjohz+rikE6cSDk9K/mBFZhRluA7C9vwDrbuwF02JQgkiMXl4NungrnoUeJMrY='
		//]
		const args = [
			'19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut',
			'sup',
			'text/plain',
			'text',
			'twetch_twtext_1628731249452.txt',
			'|',
			'1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5',
			'SET',
			'twdata_json',
			'null',
			'url',
			'null',
			'comment',
			'null',
			'mb_user',
			'null',
			'reply',
			'null',
			'type',
			'post',
			'timestamp',
			'null',
			'app',
			'twetch',
			'invoice',
			'a637579f-37d2-4e38-b40c-b95f469ef4f8',
			'|',
			'15PciHG22SNLQJXMoSUaWVi7WSqc7hCfva',
			'BITCOIN_ECDSA',
			'12tDncQvFZaZzqanupmtXpDUm42Wd4Cn4W',
			'H8G5Siy7lgWO9xNaVmz/Z2LNrHk53Gu1BlTagT4BkcGrcJZlM34gP5ADhCASnFiYxo+O+R1EqMfLbqjgAm0m9lU='
		]
		const randPriv_js = bsv.PrivateKey.fromRandom()
		const wif = 'L1BSMMgzBFNks4F4MWBzSya3duwPdd6crGyHsGxXV52bu6fTA37E'

		const start_js = new Date()

		const script_js = new bsv.Script()
		for (let each of args) {
			script_js.add(Buffer.from(each))
		}
		const scriptHex_js = script_js.toHex()
		const priv_js = new bsv.PrivateKey.fromString(wif)
		const pub_js = priv_js.toPublicKey()
		const { cipherText_js, hash_js } = eciesEphemeralEncrypt(scriptHex_js, pub_js, randPriv_js)

		const end_js = new Date()

		const asm_wasm = args.map((e) => Buffer.from(e).toString('hex')).join(' ')
		const scriptHex_wasm = Buffer.from(Script.fromASMString(asm_wasm).toHex(), 'hex')
		const priv_wasm = PrivateKey.fromWIF(wif)
		const pub_wasm = priv_wasm.getPublicKey()
		const randPriv_wasm = PrivateKey.fromWIF(randPriv_js.toString())
		const cipherText = ECIES.encrypt(scriptHex_wasm, randPriv_wasm, pub_wasm, false)
		const cipherKeys = ECIES.deriveCipherKeys(randPriv_wasm, pub_wasm)
		const hash_wasm = Buffer.concat([
			cipherKeys.get_iv(),
			cipherKeys.get_ke(),
			cipherKeys.get_km()
		]).toString('hex')
		const cipherText_wasm = Buffer.from(cipherText.toBytes()).toString('hex')

		const end_wasm = new Date()

		console.log(`js runtime: ${end_js.getTime() - start_js.getTime()}ms`)
		console.log(`wasm runtime: ${end_wasm.getTime() - end_js.getTime()}ms`)

		const asm = `0 OP_RETURN 747765746368 ${cipherText_wasm}`
		const final_script_js = bsv.Script.fromASM(asm)
		const final_script_wasm = Script.fromASMString(asm).toHex()

		const encryptedHex = final_script_js.chunks[3].buf.toString('hex')
		const buf = Buffer.from(hash_wasm, 'hex')
		const iV = buf.slice(0, 16)
		const kE = buf.slice(16, 32)

		const decryptedHex = aesCBCDecrypt(encryptedHex, kE, iV)

		assert.equal(priv_js.toString(), priv_wasm.toWIF())
		assert.equal(randPriv_js.toString(), randPriv_wasm.toWIF())
		assert.equal(pub_js.toString(), pub_wasm.toHex())
		assert.equal(cipherText_js, cipherText_wasm)
		assert.equal(hash_js, hash_wasm)
		assert.equal(scriptHex_js, scriptHex_wasm.toString('hex'))
		assert.equal(script_js.toASM(), asm_wasm)
		assert.equal(final_script_wasm, final_script_js.toHex())
		assert.equal(encryptedHex, cipherText_js)
		assert.equal(encryptedHex, cipherText_wasm)
		assert.equal(decryptedHex, scriptHex_js)
		assert.equal(decryptedHex, scriptHex_wasm.toString('hex'))
	})
})