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 }
11
12export type UserSecretData = { a: BigInteger, b: BigInteger, f: Point }
13export type UnblindedSignature = { s: BigInteger, f: Point }
14
15/**
16 * Imports a Point from hex string where X and Y coordinates were encoded as 32
17 * & 32 bytes in LittleEndian.
18 */
19export function importPointFromHex(pointHex: string) {
20 const xBuff = Buffer.from(pointHex.substr(0, 64), 'hex').reverse().toString('hex')
21 const yBuff = Buffer.from(pointHex.substr(64), 'hex').reverse().toString('hex')
22 const x = BigInteger.fromHex(xBuff)
23 const y = BigInteger.fromHex(yBuff)
24 const p = Point.fromAffine(ecparams, x, y)
25 return p
26}
27
28export function newBigFromString(s: string) {
29 let a = new BigInteger(null, null, null)
30 a.fromString(s, null)
31 return a
32}
33
34function random(bytes: number) {
35 let k: BigInteger
36 do {
37 k = BigInteger.fromByteArrayUnsigned(randomBytes(bytes)) as unknown as BigInteger
38 } while (k.toString() == '0' && k.gcd(n).toString() != '1')
39 return k
40}
41
42export function newKeyPair() {
43 const sk = random(32)
44 return { sk: sk, pk: G.multiply(sk) }
45}
46
47export function newRequestParameters() {
48 const k = random(32)
49 return { k: k, signerR: G.multiply(k) }
50}
51
52/**
53 * Blinds the message for the signer R.
54 * @param {BigInteger} m
55 * @param {Point} signerR
56 * @returns {struct} {mBlinded: BigInteger, userSecretData: {a: BigInteger, b: BigInteger, f: Point}}
57 */
58export function blind(m: BigInteger, signerR: Point): { mBlinded: BigInteger, userSecretData: UserSecretData } {
59 const u: UserSecretData = { a: BigInteger.ZERO as BigInteger, b: BigInteger.ZERO as BigInteger, f: G }
60 u.a = random(32)
61 u.b = random(32)
62
63 const aR = signerR.multiply(u.a)
64 const bG = G.multiply(u.b)
65 u.f = aR.add(bG)
66
67 const rx = u.f.affineX.mod(n)
68
69 const ainv = u.a.modInverse(n as unknown as number)
70 const ainvrx = ainv.multiply(rx)
71
72 const mHex = m.toString(16)
73 const hHex = keccak256('0x' + mHex)
74 const h = BigInteger.fromHex(hHex.slice(2))
75 const mBlinded = ainvrx.multiply(h)
76
77 return { mBlinded: mBlinded.mod(n), userSecretData: u }
78}
79
80export function blindSign(sk: BigInteger, mBlinded: BigInteger, k: BigInteger): BigInteger {
81 let sBlind = sk.multiply(mBlinded)
82 sBlind = sBlind.add(k)
83 return sBlind.mod(n)
84}
85
86/**
87 * Unblinds the blinded signature.
88 * @param blinded signature
89 * @param userSecretData
90 * @returns unblinded signature
91 */
92export function unblind(sBlind: BigInteger, userSecretData: UserSecretData): UnblindedSignature {
93 const s = userSecretData.a.multiply(sBlind).add(userSecretData.b)
94 return { s: s.mod(n), f: userSecretData.f }
95}
96
97export function verify(m: BigInteger, s: UnblindedSignature, q: Point) {
98 const sG = G.multiply(s.s)
99
100 const mHex = m.toString(16)
101 const hHex = keccak256('0x' + mHex)
102 const h = BigInteger.fromHex(hHex.slice(2))
103
104 const rx = s.f.affineX.mod(n)
105 const right = s.f.add(
106 q.multiply(
107 rx.multiply(h)
108 )
109 )
110
111 if ((sG.affineX.toString() == right.affineX.toString())
112 && (sG.affineY.toString() == right.affineY.toString())) {
113 return true
114 }
115 return false
116}