Skip to content

Commit 1812288

Browse files
committed
feat: javascript point compression
1 parent 316a610 commit 1812288

File tree

4 files changed

+147
-26
lines changed

4 files changed

+147
-26
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@ artifacts/
44
build/
55
# Aiken's default documentation export
66
docs/
7+
8+
9+
node_modules
10+
conversion/package-lock.json

conversion/index.js

Lines changed: 134 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,161 @@
11
const fs = require("fs");
2+
const bb = require("bigint-buffer");
3+
const ff = require("ffjavascript");
24

35
const proof = JSON.parse(fs.readFileSync("proof.json", "utf-8"));
46
const verificationKey = JSON.parse(fs.readFileSync("verification_key.json", "utf-8"));
57

6-
console.log("Proof", JSON.stringify(proof));
8+
async function compressedG1(point) {
9+
const curve = await ff.getCurveFromName("bls12381");
710

8-
function uncompressedFq(number) {
9-
const buffer = Buffer.alloc(48);
10-
buffer.write(BigInt(number).toString(16), 0, 48, "hex");
11-
return buffer.toString("hex");
12-
}
11+
const result = bb.toBufferBE(BigInt(point[0]), 48);
12+
const COMPRESSED = 0b10000000;
13+
const INFINITY = 0b01000000;
14+
const YBIT = 0b00100000;
15+
16+
result[0] = result[0] | COMPRESSED;
17+
18+
if (BigInt(point[2]) !== 1n) {
19+
result[0] = result[0] | INFINITY;
20+
} else {
21+
const F = curve.G1.F;
22+
23+
const x = F.fromObject(BigInt(point[0]));
24+
25+
const x3b = F.add(F.mul(F.square(x), x), curve.G1.b);
26+
const y1 = F.toObject(F.sqrt(x3b));
27+
const y2 = F.toObject(F.neg(F.sqrt(x3b)));
28+
29+
const y = BigInt(point[1]);
1330

14-
function uncompressedG1(point) {
15-
const x = uncompressedFq(point[0]);
16-
return x;
31+
if (y1 > y2 && y > y2) {
32+
result[0] = result[0] | YBIT;
33+
} else if (y1 < y2 && y > y1) {
34+
result[0] = result[0] | YBIT;
35+
}
36+
}
37+
38+
39+
return result.toString("hex");
1740
}
1841

19-
function uncompressedG2(point) {
20-
const x = uncompressedFq(point[0][0]) + uncompressedFq(point[0][1]);
21-
return x;
42+
async function compressedG2(point) {
43+
const curve = await ff.getCurveFromName("bls12381");
44+
45+
const result = Buffer.concat([bb.toBufferBE(BigInt(point[0][1]), 48), bb.toBufferBE(BigInt(point[0][0]), 48)]);
46+
const COMPRESSED = 0b10000000;
47+
const INFINITY = 0b01000000;
48+
const YBIT = 0b00100000;
49+
50+
result[0] = result[0] | COMPRESSED;
51+
52+
if (BigInt(point[2][0]) !== 1n) {
53+
result[0] = result[0] | INFINITY;
54+
} else {
55+
const F = curve.G2.F;
56+
57+
const x = F.fromObject(point[0].map(item => BigInt(item)));
58+
59+
// console.log("x", x);
60+
61+
const x3b = F.add(F.mul(F.square(x), x), curve.G2.b);
62+
const y1 = F.toObject(F.sqrt(x3b));
63+
const y2 = F.toObject(F.neg(F.sqrt(x3b)));
64+
// console.log("y1", y1);
65+
// console.log("y2", y2);
66+
// console.log("point", point[1]);
67+
68+
function greaterThan(a, b) {
69+
if (a[1] > b[1]) {
70+
return true
71+
} else if (a[1] === b[1] && a[0] > b[0]) {
72+
return true;
73+
}
74+
return false;
75+
}
76+
77+
const y = point[1].map(item => BigInt(item));
78+
79+
if (greaterThan(y1, y2) && greaterThan(y, y2)) {
80+
result[0] = result[0] | YBIT;
81+
} else if (greaterThan(y2, y1) && greaterThan(y, y1)) {
82+
result[0] = result[0] | YBIT;
83+
}
84+
}
85+
return result.toString("hex");
2286
}
2387

24-
function convertProofToUncompressed(proof) {
88+
89+
async function convertProofToUncompressed(proof) {
2590

2691
const uncompressedProof = {
27-
"pi_a": uncompressedG1(proof.pi_a),
28-
"pi_b": uncompressedG2(proof.pi_b),
29-
"pi_c": uncompressedG1(proof.pi_c),
92+
"pi_a": await compressedG1(proof.pi_a),
93+
"pi_b": await compressedG2(proof.pi_b),
94+
"pi_c": await compressedG1(proof.pi_c),
3095
}
3196

3297
return uncompressedProof;
3398
}
3499

35100

36-
function convertVerificationKeyToUncompressed(verificationKey) {
101+
async function convertVerificationKeyToUncompressed(verificationKey) {
37102
const uncompressedVerificationKey = {
38-
"vk_alpha_1": uncompressedG1(verificationKey.vk_alpha_1),
39-
"vk_beta_2": uncompressedG2(verificationKey.vk_beta_2),
40-
"vk_gamma_2": uncompressedG2(verificationKey.vk_gamma_2),
41-
"vk_delta_2": uncompressedG2(verificationKey.vk_delta_2),
42-
"IC": verificationKey.IC.map((item) => uncompressedG1(item)),
103+
"vk_alpha_1": await compressedG1(verificationKey.vk_alpha_1),
104+
"vk_beta_2": await compressedG2(verificationKey.vk_beta_2),
105+
"vk_gamma_2": await compressedG2(verificationKey.vk_gamma_2),
106+
"vk_delta_2": await compressedG2(verificationKey.vk_delta_2),
107+
"IC": verificationKey.IC.map(async (item) => await compressedG1(item)),
43108
}
44109

45110
return uncompressedVerificationKey;
46111
}
47112

48-
console.log("Uncompressed proof", JSON.stringify(convertProofToUncompressed(proof)));
113+
async function printCompressedProof() {
114+
console.log("Compressed proof", JSON.stringify(await convertProofToUncompressed(proof)));
115+
}
116+
117+
printCompressedProof();
49118

50-
console.log("\n\nUncompressed verification key", JSON.stringify(convertVerificationKeyToUncompressed(verificationKey)));
119+
async function printCompressedVerificationKey() {
120+
console.log("\n\nUncompressed verification key", JSON.stringify(await convertVerificationKeyToUncompressed(verificationKey)));
121+
}
122+
123+
printCompressedVerificationKey();
124+
125+
126+
async function ffTest() {
127+
const curve = await ff.getCurveFromName("bls12381");
128+
129+
const point = proof.pi_c;
130+
131+
const xBufferBE = bb.toBufferBE(BigInt(point[0]), 48);
132+
133+
console.log("Point x buffer BE", new Uint8Array(xBufferBE));
134+
135+
const xBufferLE = bb.toBufferLE(BigInt(point[0]), 48);
136+
137+
console.log("Point x buffer LE", new Uint8Array(xBufferLE));
138+
139+
140+
const g1Element = curve.G1.fromObject(point.map((item) => BigInt(item).toString(16)));
141+
142+
console.log("G1 element", g1Element);
143+
144+
console.log("G1 Element is valid", curve.G1.isValid(g1Element));
145+
146+
const buff = new Uint8Array(48);
147+
148+
curve.G1.toRprCompressed(buff, 0, g1Element);
149+
150+
console.log("G1 element Compressed", buff);
151+
152+
console.log("MSB", buff[0].toString(2));
153+
154+
const g1ElementFromCompressed = curve.G1.fromRprCompressed(buff, 0);
155+
156+
console.log("G1 from compressed", g1ElementFromCompressed);
157+
158+
console.log("G1 from compressed is valid", curve.G1.isValid(g1ElementFromCompressed));
159+
}
51160

161+
// ffTest();

conversion/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,9 @@
99
},
1010
"keywords": [],
1111
"author": "",
12-
"license": "ISC"
12+
"license": "ISC",
13+
"dependencies": {
14+
"bigint-buffer": "^1.1.5",
15+
"ffjavascript": "^0.2.63"
16+
}
1317
}

lib/ak-381/groth16.ak

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ pub fn groth_verify(
8585
// }
8686

8787
test bls_point_construction() {
88-
let point_1 = #<Bls12_381, G1>"184bb665c37ff561a89ec2122dd343f20e0f4cbcaec84e3c3052ea81d1834e192c426074b02ed3dca4e7676ce4ce48ba"
88+
let point_g1 = #<Bls12_381, G1>"b28cb29bc282be68df977b35eb9d8e98b3a0a3fc7c372990bddc50419ca86693e491755338fed4fb42231a7c081252ce"
89+
90+
let point_g2 = #<Bls12_381, G2>"8cd68a7186a908212680a0234d8210c20328f8fb3ce1d69c9aec9330a5802d6cfaf6d7cf3176133221c19188590cb4141874ea7bbfcb9872931e115d882c46b90c3dcbcee10062d1c9b9b0a691d7bec7d2735f06495c7f71dea210e55b2782df"
91+
8992
True
9093
}

0 commit comments

Comments
 (0)