Skip to content

Commit

Permalink
Complete BLS short signature support
Browse files Browse the repository at this point in the history
  • Loading branch information
randombit committed Aug 24, 2023
1 parent b082d41 commit c5e0e07
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 2 deletions.
62 changes: 61 additions & 1 deletion src/abstract/bls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type Fp = bigint; // Can be different field?
// prettier-ignore
const _2n = BigInt(2), _3n = BigInt(3);

export type ShortSignatureCoder<Fp> = {
fromHex(hex: Hex): ProjPointType<Fp>;
toRawBytes(point: ProjPointType<Fp>): Uint8Array;
toHex(point: ProjPointType<Fp>): string;
};

export type SignatureCoder<Fp2> = {
fromHex(hex: Hex): ProjPointType<Fp2>;
toRawBytes(point: ProjPointType<Fp2>): Uint8Array;
Expand All @@ -35,6 +41,7 @@ export type SignatureCoder<Fp2> = {

export type CurveType<Fp, Fp2, Fp6, Fp12> = {
G1: Omit<CurvePointsType<Fp>, 'n'> & {
ShortSignature: SignatureCoder<Fp>;
mapToCurve: htf.MapToCurve<Fp>;
htfDefaults: htf.Opts;
};
Expand Down Expand Up @@ -70,10 +77,15 @@ export type CurveType<Fp, Fp2, Fp6, Fp12> = {

export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
getPublicKey: (privateKey: PrivKey) => Uint8Array;
getPublicKeyForShortSignatures: (privateKey: PrivKey) => Uint8Array;
sign: {
(message: Hex, privateKey: PrivKey): Uint8Array;
(message: ProjPointType<Fp2>, privateKey: PrivKey): ProjPointType<Fp2>;
};
signShortSignature: {
(message: Hex, privateKey: PrivKey): Uint8Array;
(message: ProjPointType<Fp>, privateKey: PrivKey): ProjPointType<Fp>;
};
verify: (
signature: Hex | ProjPointType<Fp2>,
message: Hex | ProjPointType<Fp2>,
Expand All @@ -97,11 +109,16 @@ export type CurveFn<Fp, Fp2, Fp6, Fp12> = {
(signatures: Hex[]): Uint8Array;
(signatures: ProjPointType<Fp2>[]): ProjPointType<Fp2>;
};
aggregateShortSignatures: {
(signatures: Hex[]): Uint8Array;
(signatures: ProjPointType<Fp>[]): ProjPointType<Fp>;
};
millerLoop: (ell: [Fp2, Fp2, Fp2][], g1: [Fp, Fp]) => Fp12;
pairing: (P: ProjPointType<Fp>, Q: ProjPointType<Fp2>, withFinalExponent?: boolean) => Fp12;
G1: CurvePointsRes<Fp> & ReturnType<typeof htf.createHasher<Fp>>;
G2: CurvePointsRes<Fp2> & ReturnType<typeof htf.createHasher<Fp2>>;
Signature: SignatureCoder<Fp2>;
ShortSignature: ShortSignatureCoder<Fp>;
params: {
x: bigint;
r: bigint;
Expand Down Expand Up @@ -238,6 +255,7 @@ export function bls<Fp2, Fp6, Fp12>(
})
);

const { ShortSignature } = CURVE.G1;
const { Signature } = CURVE.G2;

// Calculates bilinear pairing
Expand Down Expand Up @@ -273,12 +291,18 @@ export function bls<Fp2, Fp6, Fp12>(
: (G2.hashToCurve(ensureBytes('point', point), htfOpts) as G2);
}

// Multiplies generator by private key.
// Multiplies generator (G1) by private key.
// P = pk x G
function getPublicKey(privateKey: PrivKey): Uint8Array {
return G1.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true);
}

// Multiplies generator (G2) by private key.
// P = pk x G
function getPublicKeyForShortSignatures(privateKey: PrivKey): Uint8Array {
return G2.ProjectivePoint.fromPrivateKey(privateKey).toRawBytes(true);
}

// Executes `hashToCurve` on the message and then multiplies the result by private key.
// S = pk x H(m)
function sign(message: Hex, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): Uint8Array;
Expand All @@ -291,6 +315,24 @@ export function bls<Fp2, Fp6, Fp12>(
return Signature.toRawBytes(sigPoint);
}

function signShortSignature(
message: Hex,
privateKey: PrivKey,
htfOpts?: htf.htfBasicOpts
): Uint8Array;
function signShortSignature(message: G1, privateKey: PrivKey, htfOpts?: htf.htfBasicOpts): G1;
function signShortSignature(
message: G1Hex,
privateKey: PrivKey,
htfOpts?: htf.htfBasicOpts
): Uint8Array | G1 {
const msgPoint = normP1Hash(message, htfOpts);
msgPoint.assertValidity();
const sigPoint = msgPoint.multiply(G1.normPrivateKeyToScalar(privateKey));
if (message instanceof G1.ProjectivePoint) return sigPoint;
return ShortSignature.toRawBytes(sigPoint);
}

// Checks if pairing of public key & hash is equal to pairing of generator & signature.
// e(P, H(m)) == e(G, S)
function verify(
Expand Down Expand Up @@ -361,6 +403,20 @@ export function bls<Fp2, Fp6, Fp12>(
return Signature.toRawBytes(aggAffine);
}

// Adds a bunch of signature points together.
function aggregateShortSignatures(signatures: Hex[]): Uint8Array;
function aggregateShortSignatures(signatures: G1[]): G1;
function aggregateShortSignatures(signatures: G1Hex[]): Uint8Array | G1 {
if (!signatures.length) throw new Error('Expected non-empty array');
const agg = signatures.map(normP1).reduce((sum, s) => sum.add(s), G1.ProjectivePoint.ZERO);
const aggAffine = agg; //.toAffine();
if (signatures[0] instanceof G1.ProjectivePoint) {
aggAffine.assertValidity();
return aggAffine;
}
return ShortSignature.toRawBytes(aggAffine);
}

// https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
// e(G, S) = e(G, SUM(n)(Si)) = MUL(n)(e(G, Si))
function verifyBatch(
Expand Down Expand Up @@ -403,17 +459,21 @@ export function bls<Fp2, Fp6, Fp12>(

return {
getPublicKey,
getPublicKeyForShortSignatures,
sign,
signShortSignature,
verify,
verifyBatch,
verifyShortSignature,
aggregatePublicKeys,
aggregateSignatures,
aggregateShortSignatures,
millerLoop,
pairing,
G1,
G2,
Signature,
ShortSignature,
fields: {
Fr,
Fp,
Expand Down
38 changes: 37 additions & 1 deletion src/bls12-381.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,18 @@ const S_BIT_POS = Fp.BITS + 2; // S_bit, sign bit for serialization flag
// Compressed point of infinity
const COMPRESSED_ZERO = Fp.toBytes(bitSet(bitSet(_0n, I_BIT_POS, true), S_BIT_POS, true)); // set compressed & point-at-infinity bits

function signatureG1ToRawBytes(point: ProjPointType<Fp>) {
point.assertValidity();
const isZero = point.equals(bls12_381.G1.ProjectivePoint.ZERO);
const { x, y } = point.toAffine();
if (isZero) return COMPRESSED_ZERO.slice();
const P = Fp.ORDER;
let num;
num = bitSet(x, C_BIT_POS, Boolean((y * _2n) / P)); // set aflag
num = bitSet(num, S_BIT_POS, true);
return numberToBytesBE(num, Fp.BYTES);
}

function signatureG2ToRawBytes(point: ProjPointType<Fp2>) {
// NOTE: by some reasons it was missed in bls12-381, looks like bug
point.assertValidity();
Expand Down Expand Up @@ -1075,7 +1087,7 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
),
a: Fp.ZERO,
b: _4n,
htfDefaults: { ...htfDefaults, m: 1 },
htfDefaults: { ...htfDefaults, m: 1, DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_' },

This comment has been minimized.

Copy link
@secure12

secure12 Jan 8, 2024

Contributor

Should not encodeDST be overridden as well?

wrapPrivateKey: true,
allowInfinityPoint: true,
// Checks is the point resides in prime-order subgroup.
Expand Down Expand Up @@ -1162,6 +1174,30 @@ export const bls12_381: CurveFn<Fp, Fp2, Fp6, Fp12> = bls({
}
}
},
ShortSignature: {
fromHex(hex: Hex): ProjPointType<Fp> {
const bytes = ensureBytes('signatureHex', hex, 48);

const P = Fp.ORDER;
const compressedValue = bytesToNumberBE(bytes);
const bflag = bitGet(compressedValue, I_BIT_POS);
// Zero
if (bflag === _1n) return bls12_381.G1.ProjectivePoint.ZERO;
const x = Fp.create(compressedValue & Fp.MASK);
const right = Fp.add(Fp.pow(x, _3n), Fp.create(bls12_381.params.G1b)); // y² = x³ + b
let y = Fp.sqrt(right);
if (!y) throw new Error('Invalid compressed G1 point');
const aflag = bitGet(compressedValue, C_BIT_POS);
if ((y * _2n) / P !== aflag) y = Fp.neg(y);
return bls12_381.G1.ProjectivePoint.fromAffine({ x, y });
},
toRawBytes(point: ProjPointType<Fp>) {
return signatureG1ToRawBytes(point);
},
toHex(point: ProjPointType<Fp>) {
return bytesToHex(signatureG1ToRawBytes(point));
},
},
},
// G2 is the order-q subgroup of E2(Fp²) : y² = x³+4(1+√−1),
// where Fp2 is Fp[√−1]/(x2+1). #E2(Fp2 ) = h2q, where
Expand Down
40 changes: 40 additions & 0 deletions test/bls12-381.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { bls12_381 as bls } from '../esm/bls12-381.js';

import zkVectors from './bls12-381/zkcrypto/converted.json' assert { type: 'json' };
import pairingVectors from './bls12-381/go_pairing_vectors/pairing.json' assert { type: 'json' };
const G1_VECTORS = readFileSync('./test/bls12-381/bls12-381-g1-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
.map((l) => l.split(':'));
const G2_VECTORS = readFileSync('./test/bls12-381/bls12-381-g2-test-vectors.txt', 'utf-8')
.trim()
.split('\n')
Expand Down Expand Up @@ -852,6 +856,13 @@ describe('bls12-381/basic', () => {
});
// should aggregate signatures

should(`produce correct short signatures (${G1_VECTORS.length} vectors)`, () => {
for (let vector of G1_VECTORS) {
const [priv, msg, expected] = vector;
const sig = bls.signShortSignature(msg, priv);
deepStrictEqual(bytesToHex(sig), expected);
}
});
should(`produce correct signatures (${G2_VECTORS.length} vectors)`, () => {
for (let vector of G2_VECTORS) {
const [priv, msg, expected] = vector;
Expand Down Expand Up @@ -1182,6 +1193,35 @@ describe('verify()', () => {
deepStrictEqual(res, false);
}
});
should('verify signed message (short signatures)', () => {
for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G1_VECTORS[i];
const sig = bls.signShortSignature(msg, priv);
const pub = bls.getPublicKeyForShortSignatures(priv);
const res = bls.verifyShortSignature(sig, msg, pub);
deepStrictEqual(res, true, `${priv}-${msg}`);
}
});
should('not verify signature with wrong message (short signatures)', () => {
for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G1_VECTORS[i];
const invMsg = G1_VECTORS[i + 1][1];
const sig = bls.signShortSignature(msg, priv);
const pub = bls.getPublicKeyForShortSignatures(priv);
const res = bls.verifyShortSignature(sig, invMsg, pub);
deepStrictEqual(res, false);
}
});
should('not verify signature with wrong key', () => {
for (let i = 0; i < NUM_RUNS; i++) {
const [priv, msg] = G1_VECTORS[i];
const sig = bls.signShortSignature(msg, priv);
const invPriv = G1_VECTORS[i + 1][1].padStart(64, '0');
const invPub = bls.getPublicKeyForShortSignatures(invPriv);
const res = bls.verifyShortSignature(sig, msg, invPub);
deepStrictEqual(res, false);
}
});
describe('batch', () => {
should('verify multi-signature', () => {
fc.assert(
Expand Down
Loading

0 comments on commit c5e0e07

Please sign in to comment.