arnaucube
arnaucube/blindsecp256k1-jsPublic Self-hosted repository
blindsecp256k1-js/src/index.ts
src/index.tsRaw
  1import { randomBytes } from 'crypto'
  2import * as BigInteger from 'bigi'
  3import { getCurveByName, Point } from 'ecurve'
  4import { keccak256 } from '@ethersproject/keccak256'
  5
  6const ecparams = getCurveByName('secp256k1')
  7const G = ecparams.G
  8const n = ecparams.n as BigInteger
  9
 10export { ecparams }
 11export { BigInteger }
 12export { Point }
 13
 14export type UserSecretData = { a: BigInteger, b: BigInteger, f: Point }
 15export type UnblindedSignature = { s: BigInteger, f: Point }
 16
 17/**
 18 * Imports a Point from hex string where X and Y coordinates were encoded as 32
 19 * & 32 bytes in LittleEndian.
 20 */
 21export function pointFromHex(pointHex: string) {
 22    const xBuff = Buffer.from(pointHex.substr(0, 64), 'hex').reverse().toString('hex')
 23    const yBuff = Buffer.from(pointHex.substr(64), 'hex').reverse().toString('hex')
 24    const x = BigInteger.fromHex(xBuff)
 25    const y = BigInteger.fromHex(yBuff)
 26    const p = Point.fromAffine(ecparams, x, y)
 27    return p
 28}
 29
 30export function pointToHex(point: Point): string {
 31    const buffX = point.affineX.toBuffer(32).reverse()
 32    const buffY = point.affineY.toBuffer(32).reverse()
 33
 34    return buffX.toString("hex") + buffY.toString("hex")
 35}
 36
 37export function signatureFromHex(hexSignature: string): UnblindedSignature {
 38    if (!hexSignature || hexSignature.length != 192) throw new Error("Invalid hex signature (96 bytes expected)")
 39
 40    const s = BigInteger.fromBuffer(Buffer.from(hexSignature.substr(0, 64), "hex").reverse())
 41    const f = pointFromHex(hexSignature.substr(64))
 42    return { s, f }
 43}
 44
 45export function signatureToHex(signature: UnblindedSignature): string {
 46    if (!signature || !signature.f || !signature.s) throw new Error("The signature is empty")
 47    const { f, s } = signature
 48
 49    // hex(swapEndiannes(s) ) + hex(f)
 50    const flippedHexS = s.toBuffer(32).reverse().toString("hex")
 51    return flippedHexS + pointToHex(f)
 52}
 53
 54export function messageToBigNumber(message: string) {
 55    const msg = Buffer.from(message, 'utf8')
 56    return BigInteger.fromBuffer(msg)
 57}
 58
 59export function newBigFromString(s: string) {
 60    let a = new BigInteger(null, null, null)
 61    a.fromString(s, null)
 62    return a
 63}
 64
 65export function newKeyPair() {
 66    const sk = random(32)
 67    return { sk: sk, pk: G.multiply(sk) }
 68}
 69
 70export function newRequestParameters() {
 71    const k = random(32)
 72    return { k: k, signerR: G.multiply(k) }
 73}
 74
 75/**
 76 * Blinds the message for the signer R.
 77 * @param {BigInteger} m
 78 * @param {Point} signerR
 79 * @returns {struct} {mBlinded: BigInteger, userSecretData: {a: BigInteger, b: BigInteger, f: Point}}
 80 */
 81export function blind(m: BigInteger, signerR: Point): { mBlinded: BigInteger, userSecretData: UserSecretData } {
 82    const u: UserSecretData = { a: BigInteger.ZERO as BigInteger, b: BigInteger.ZERO as BigInteger, f: G }
 83    u.a = random(32)
 84    u.b = random(32)
 85
 86    const aR = signerR.multiply(u.a)
 87    const bG = G.multiply(u.b)
 88    u.f = aR.add(bG)
 89
 90    const rx = u.f.affineX.mod(n)
 91
 92    const ainv = u.a.modInverse(n as unknown as number)
 93    const ainvrx = ainv.multiply(rx)
 94
 95    const mHex = m.toString(16)
 96    const hHex = keccak256('0x' + evenHex(mHex)).substr(2)
 97    const h = BigInteger.fromHex(hHex)
 98
 99    const mBlinded = ainvrx.multiply(h)
100
101    return { mBlinded: mBlinded.mod(n), userSecretData: u }
102}
103
104export function blindSign(sk: BigInteger, mBlinded: BigInteger, k: BigInteger): BigInteger {
105    let sBlind = sk.multiply(mBlinded)
106    sBlind = sBlind.add(k)
107    return sBlind.mod(n)
108}
109
110/**
111 * Unblinds the blinded signature.
112 * @param blinded signature
113 * @param userSecretData
114 * @returns unblinded signature
115 */
116export function unblind(sBlind: BigInteger, userSecretData: UserSecretData): UnblindedSignature {
117    const s = userSecretData.a.multiply(sBlind).add(userSecretData.b)
118    return { s: s.mod(n), f: userSecretData.f }
119}
120
121export function verify(m: BigInteger, s: UnblindedSignature, q: Point) {
122    const sG = G.multiply(s.s)
123
124    const mHex = m.toString(16)
125    const hHex = keccak256('0x' + evenHex(mHex)).substr(2)
126    const h = BigInteger.fromHex(hHex)
127
128    const rx = s.f.affineX.mod(n)
129    const right = s.f.add(
130        q.multiply(
131            rx.multiply(h)
132        )
133    )
134
135    if ((sG.affineX.toString() == right.affineX.toString())
136        && (sG.affineY.toString() == right.affineY.toString())) {
137        return true
138    }
139    return false
140}
141
142// HELPERS
143
144function random(bytes: number) {
145    let k: BigInteger
146    do {
147        k = BigInteger.fromByteArrayUnsigned(randomBytes(bytes)) as unknown as BigInteger
148    } while (k.toString() == '0' && k.gcd(n).toString() != '1')
149    return k
150}
151
152export function evenHex(hexString: string) {
153    if ((hexString.length % 2) != 0) {
154        hexString = "0" + hexString
155    }
156    return hexString
157}