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' + zeroPad(mHex, 32)).substr(2)
97 const h = BigInteger.fromHex(hHex)
98 const mBlinded = ainvrx.multiply(h)
99
100 return { mBlinded: mBlinded.mod(n), userSecretData: u }
101}
102
103export function blindSign(sk: BigInteger, mBlinded: BigInteger, k: BigInteger): BigInteger {
104 let sBlind = sk.multiply(mBlinded)
105 sBlind = sBlind.add(k)
106 return sBlind.mod(n)
107}
108
109/**
110 * Unblinds the blinded signature.
111 * @param blinded signature
112 * @param userSecretData
113 * @returns unblinded signature
114 */
115export function unblind(sBlind: BigInteger, userSecretData: UserSecretData): UnblindedSignature {
116 const s = userSecretData.a.multiply(sBlind).add(userSecretData.b)
117 return { s: s.mod(n), f: userSecretData.f }
118}
119
120export function verify(m: BigInteger, s: UnblindedSignature, q: Point) {
121 const sG = G.multiply(s.s)
122
123 const mHex = m.toString(16)
124 const hHex = keccak256('0x' + zeroPad(mHex, 32)).substr(2)
125 const h = BigInteger.fromHex(hHex)
126
127 const rx = s.f.affineX.mod(n)
128 const right = s.f.add(
129 q.multiply(
130 rx.multiply(h)
131 )
132 )
133
134 if ((sG.affineX.toString() == right.affineX.toString())
135 && (sG.affineY.toString() == right.affineY.toString())) {
136 return true
137 }
138 return false
139}
140
141// HELPERS
142
143function random(bytes: number) {
144 let k: BigInteger
145 do {
146 k = BigInteger.fromByteArrayUnsigned(randomBytes(bytes)) as unknown as BigInteger
147 } while (k.toString() == '0' && k.gcd(n).toString() != '1')
148 return k
149}
150
151function zeroPad(hexString: string, byteLength: number) {
152 if (hexString.length > (byteLength * 2)) throw new Error("Out of bounds")
153 while (hexString.length < (byteLength * 2)) {
154 hexString = "0" + hexString
155 }
156 return hexString
157}