import { randomBytes } from 'crypto' import * as BigInteger from 'bigi' import { getCurveByName, Point } from 'ecurve' import { keccak256 } from '@ethersproject/keccak256' const ecparams = getCurveByName('secp256k1') const G = ecparams.G const n = ecparams.n as BigInteger export { ecparams } export { BigInteger } export { Point } export type UserSecretData = { a: BigInteger, b: BigInteger, f: Point } export type UnblindedSignature = { s: BigInteger, f: Point } /** * Imports a Point from hex string where X and Y coordinates were encoded as 32 * & 32 bytes in LittleEndian. */ export function pointFromHex(pointHex: string) { const xBuff = Buffer.from(pointHex.substr(0, 64), 'hex').reverse().toString('hex') const yBuff = Buffer.from(pointHex.substr(64), 'hex').reverse().toString('hex') const x = BigInteger.fromHex(xBuff) const y = BigInteger.fromHex(yBuff) const p = Point.fromAffine(ecparams, x, y) return p } export function pointToHex(point: Point): string { const buffX = point.affineX.toBuffer(32).reverse() const buffY = point.affineY.toBuffer(32).reverse() return buffX.toString("hex") + buffY.toString("hex") } export function signatureFromHex(hexSignature: string): UnblindedSignature { if (!hexSignature || hexSignature.length != 192) throw new Error("Invalid hex signature (96 bytes expected)") const s = BigInteger.fromBuffer(Buffer.from(hexSignature.substr(0, 64), "hex").reverse()) const f = pointFromHex(hexSignature.substr(64)) return { s, f } } export function signatureToHex(signature: UnblindedSignature): string { if (!signature || !signature.f || !signature.s) throw new Error("The signature is empty") const { f, s } = signature // hex(swapEndiannes(s) ) + hex(f) const flippedHexS = s.toBuffer(32).reverse().toString("hex") return flippedHexS + pointToHex(f) } export function messageToBigNumber(message: string) { const msg = Buffer.from(message, 'utf8') return BigInteger.fromBuffer(msg) } export function newBigFromString(s: string) { let a = new BigInteger(null, null, null) a.fromString(s, null) return a } export function newKeyPair() { const sk = random(32) return { sk: sk, pk: G.multiply(sk) } } export function newRequestParameters() { const k = random(32) return { k: k, signerR: G.multiply(k) } } /** * Blinds the message for the signer R. * @param {BigInteger} m * @param {Point} signerR * @returns {struct} {mBlinded: BigInteger, userSecretData: {a: BigInteger, b: BigInteger, f: Point}} */ export function blind(m: BigInteger, signerR: Point): { mBlinded: BigInteger, userSecretData: UserSecretData } { const u: UserSecretData = { a: BigInteger.ZERO as BigInteger, b: BigInteger.ZERO as BigInteger, f: G } u.a = random(32) u.b = random(32) const aR = signerR.multiply(u.a) const bG = G.multiply(u.b) u.f = aR.add(bG) const rx = u.f.affineX.mod(n) const ainv = u.a.modInverse(n as unknown as number) const ainvrx = ainv.multiply(rx) const mHex = m.toString(16) const hHex = keccak256('0x' + evenHex(mHex)).substr(2) const h = BigInteger.fromHex(hHex) const mBlinded = ainvrx.multiply(h) return { mBlinded: mBlinded.mod(n), userSecretData: u } } export function blindSign(sk: BigInteger, mBlinded: BigInteger, k: BigInteger): BigInteger { let sBlind = sk.multiply(mBlinded) sBlind = sBlind.add(k) return sBlind.mod(n) } /** * Unblinds the blinded signature. * @param blinded signature * @param userSecretData * @returns unblinded signature */ export function unblind(sBlind: BigInteger, userSecretData: UserSecretData): UnblindedSignature { const s = userSecretData.a.multiply(sBlind).add(userSecretData.b) return { s: s.mod(n), f: userSecretData.f } } export function verify(m: BigInteger, s: UnblindedSignature, q: Point) { const sG = G.multiply(s.s) const mHex = m.toString(16) const hHex = keccak256('0x' + evenHex(mHex)).substr(2) const h = BigInteger.fromHex(hHex) const rx = s.f.affineX.mod(n) const right = s.f.add( q.multiply( rx.multiply(h) ) ) if ((sG.affineX.toString() == right.affineX.toString()) && (sG.affineY.toString() == right.affineY.toString())) { return true } return false } // HELPERS function random(bytes: number) { let k: BigInteger do { k = BigInteger.fromByteArrayUnsigned(randomBytes(bytes)) as unknown as BigInteger } while (k.toString() == '0' && k.gcd(n).toString() != '1') return k } export function evenHex(hexString: string) { if ((hexString.length % 2) != 0) { hexString = "0" + hexString } return hexString }