diff --git a/dagger/por/backends/backend_blst.nim b/dagger/por/backends/backend_blst.nim new file mode 100644 index 00000000..1c27109a --- /dev/null +++ b/dagger/por/backends/backend_blst.nim @@ -0,0 +1,111 @@ +## Nim-POS +## Copyright (c) 2022 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. + +# Implementation of the BLS-based public PoS scheme from +# Shacham H., Waters B., "Compact Proofs of Retrievability" +# using pairing over BLS12-381 ECC + +import blscurve +import blscurve/blst/blst_abi + +type + ec_SecretKey* = blscurve.SecretKey + ec_PublicKey* = blscurve.PublicKey + ec_p1* = blst_p1 + ec_p2* = blst_p2 + ec_scalar* = blst_scalar + ec_fr* = blst_fr + ec_signature* = Signature + +# these need to be template as a workaround for const +template EC_G1* : blst_p1_affine = BLS12_381_G1 +template EC_G2* : blst_p2_affine = BLS12_381_G2 + +let + ec_p1_from_affine* = blst_p1_from_affine + ec_scalar_from_bendian* = blst_scalar_from_bendian + ec_scalar_fr_check* = blst_scalar_fr_check + ec_p2_from_affine* = blst_p2_from_affine + ec_p2_mult* = blst_p2_mult + ec_p1_mult* = blst_p1_mult + ec_p1_add_or_double* = blst_p1_add_or_double + ec_fr_from_scalar* = blst_fr_from_scalar + ec_fr_mul* = blst_fr_mul + ec_scalar_from_fr* = blst_scalar_from_fr + ec_fr_add* = blst_fr_add + ec_p1_on_curve* = blst_p1_on_curve + ec_keygen* = blscurve.keyGen + +func ec_export_raw*(signature: Signature): array[96, byte] {.inline, noinit.} = + blscurve.exportRaw(signature) + +proc ec_sign*[T: byte|char](secretKey: SecretKey, message: openarray[T]): Signature = + blscurve.sign(secretKey, message) + +proc ec_hash_to_g1*[T,U,V: byte|char](dst: var blst_p1; + msg: openArray[T]; + domainSepTag: openArray[U]; + aug: openArray[V]) = + blst_hash_to_g1(dst, msg, domainSepTag, aug) + +proc pairing(a: ec_p1, b: ec_p2): blst_fp12 = + ## Calculate pairing G_1,G_2 -> G_T + var aa: blst_p1_affine + var bb: blst_p2_affine + blst_p1_to_affine(aa, a) + blst_p2_to_affine(bb, b) + var l: blst_fp12 + blst_miller_loop(l, bb, aa) + blst_final_exp(result, l) + +proc verifyPairingsNaive(a1: blst_p1, a2: blst_p2, b1: blst_p1, b2: blst_p2) : bool = + let e1 = pairing(a1, a2) + let e2 = pairing(b1, b2) + return e1 == e2 + +proc verifyPairingsNeg(a1: ec_p1, a2: ec_p2, b1: ec_p1, b2: ec_p2) : bool = + ## Faster pairing verification using 2 miller loops but ony one final exponentiation + ## based on https://github.com/benjaminion/c-kzg/blob/main/src/bls12_381.c + var + loop0, loop1, gt_point: blst_fp12 + aa1, bb1: blst_p1_affine + aa2, bb2: blst_p2_affine + + var a1neg = a1 + blst_p1_cneg(a1neg, 1) + + blst_p1_to_affine(aa1, a1neg) + blst_p1_to_affine(bb1, b1) + blst_p2_to_affine(aa2, a2) + blst_p2_to_affine(bb2, b2) + + blst_miller_loop(loop0, aa2, aa1) + blst_miller_loop(loop1, bb2, bb1) + + blst_fp12_mul(gt_point, loop0, loop1) + blst_final_exp(gt_point, gt_point) + + return blst_fp12_is_one(gt_point).bool + +proc verifyPairings*(a1: ec_p1, a2: ec_p2, b1: ec_p1, b2: ec_p2) : bool = + ## Wrapper to select verify pairings implementation + verifyPairingsNaive(a1, a2, b1, b2) + #verifyPairingsNeg(a1, a2, b1, b2) + +func ec_from_bytes*( + obj: var (Signature|ProofOfPossession), + raw: array[96, byte] or array[192, byte] + ): bool {.inline.} = + fromBytes(obj, raw) + +func ec_verify*[T: byte|char]( + publicKey: PublicKey, + message: openarray[T], + signature: Signature) : bool = + verify(publicKey, message, signature) \ No newline at end of file diff --git a/dagger/por/por.nim b/dagger/por/por.nim index 9018aa96..5a8cf03b 100644 --- a/dagger/por/por.nim +++ b/dagger/por/por.nim @@ -78,8 +78,8 @@ # The size of the proof is instead # s * 32 + 48 bytes -import blscurve -import blscurve/blst/blst_abi +import ./backends/backend_blst + import ../rng import endians @@ -90,25 +90,26 @@ const bytespersector = 31 # length in bytes of the unique (random) name const namelen = 512 + type # a single sector ZChar = array[bytespersector, byte] # secret key combining the metadata signing key and the POR generation key SecretKey = object - signkey: blscurve.SecretKey - key: blst_scalar + signkey: ec_SecretKey + key: ec_scalar # public key combining the metadata signing key and the POR validation key PublicKey = object - signkey: blscurve.PublicKey - key: blst_p2 + signkey: ec_PublicKey + key: ec_p2 # POR metadata (called "file tag t_0" in the original paper) TauZero = object name: array[namelen, byte] n: int64 - u: seq[blst_p1] + u: seq[ec_p1] # signed POR metadata (called "signed file tag t" in the original paper) Tau = object @@ -118,50 +119,50 @@ type # PoR query element QElement = object I: int64 - V: blst_scalar + V: ec_scalar -proc fromBytesBE(a: array[32, byte]): blst_scalar = +proc fromBytesBE(a: array[32, byte]): ec_scalar = ## Convert data to blst native form - blst_scalar_from_bendian(result, a) - doAssert(blst_scalar_fr_check(result).bool) + ec_scalar_from_bendian(result, a) + doAssert(ec_scalar_fr_check(result).bool) -proc fromBytesBE(a: openArray[byte]): blst_scalar = +proc fromBytesBE(a: openArray[byte]): ec_scalar = ## Convert data to blst native form var b: array[32, byte] doAssert(a.len <= b.len) let d = b.len - a.len for i in 0 ..< a.len: b[i+d] = a[i] - blst_scalar_from_bendian(result, b) - doAssert(blst_scalar_fr_check(result).bool) + ec_scalar_from_bendian(result, b) + doAssert(ec_scalar_fr_check(result).bool) proc getSector(f: File, blockid: int64, sectorid: int64, spb: int64): ZChar = ## Read file sector at given postion f.setFilePos((blockid * spb + sectorid) * sizeof(result)) let r = f.readBytes(result, 0, sizeof(result)) -proc rndScalar(scalar: var blst_scalar): void = +proc rndScalar(scalar: var ec_scalar): void = ## Generate random scalar within the subroup order r var scal{.noInit.}: array[32, byte] while true: for val in scal.mitems: val = byte Rng.instance.rand(0xFF) - scalar.blst_scalar_from_bendian(scal) - if blst_scalar_fr_check(scalar).bool: + scalar.ec_scalar_from_bendian(scal) + if ec_scalar_fr_check(scalar).bool: break -proc rndP2(x: var blst_p2, scalar: var blst_scalar): void = +proc rndP2(x: var ec_p2, scalar: var ec_scalar): void = ## Generate random point on G2 - x.blst_p2_from_affine(BLS12_381_G2) # init from generator + x.ec_p2_from_affine(EC_G2) # init from generator scalar.rndScalar() - x.blst_p2_mult(x, scalar, 255) + x.ec_p2_mult(x, scalar, 255) -proc rndP1(x: var blst_p1, scalar: var blst_scalar): void = +proc rndP1(x: var ec_p1, scalar: var ec_scalar): void = ## Generate random point on G1 - x.blst_p1_from_affine(BLS12_381_G1) # init from generator + x.ec_p1_from_affine(EC_G1) # init from generator scalar.rndScalar() - x.blst_p1_mult(x, scalar, 255) + x.ec_p1_mult(x, scalar, 255) let posKeygen = rndP2 ## Generate POS key pair @@ -174,7 +175,7 @@ proc keygen*(): (PublicKey, SecretKey) = for b in ikm.mitems: b = byte Rng.instance.rand(0xFF) - doAssert ikm.keyGen(pk.signkey, sk.signkey) + doAssert ikm.ec_keygen(pk.signkey, sk.signkey) posKeygen(pk.key, sk.key) return (pk, sk) @@ -190,12 +191,12 @@ proc split(f: File, s: int64): int64 = return n -proc hashToG1[T: byte|char](msg: openArray[T]): blst_p1 = +proc hashToG1[T: byte|char](msg: openArray[T]): ec_p1 = ## Hash to curve with Dagger specific domain separation const dst = "DAGGER-PROOF-OF-CONCEPT" - result.blst_hash_to_g1(msg, dst, aug = "") + result.ec_hash_to_g1(msg, dst, aug = "") -proc hashNameI(name: array[namelen, byte], i: int64): blst_p1 = +proc hashNameI(name: array[namelen, byte], i: int64): ec_p1 = ## Calculate unique filname and block index based hash # # naive implementation, hashing a long string representation @@ -208,20 +209,20 @@ proc hashNameI(name: array[namelen, byte], i: int64): blst_p1 = bigEndian64(addr(namei[sizeof(name)]), unsafeAddr(i)) return hashToG1(namei) -proc generateAuthenticatorNaive(i: int64, s: int64, t: TauZero, f: File, ssk: SecretKey): blst_p1 = +proc generateAuthenticatorNaive(i: int64, s: int64, t: TauZero, f: File, ssk: SecretKey): ec_p1 = ## Naive implementation of authenticator as in the S&W paper. ## With the paper's multiplicative notation: ## \sigmai=\(H(file||i)\cdot\prod{j=0}^{s-1}{uj^{m[i][j]}})^{\alpha} - var sum: blst_p1 + var sum: ec_p1 for j in 0 ..< s: - var prod: blst_p1 - prod.blst_p1_mult(t.u[j], fromBytesBE(getSector(f, i, j, s)), 255) - sum.blst_p1_add_or_double(sum, prod) + var prod: ec_p1 + prod.ec_p1_mult(t.u[j], fromBytesBE(getSector(f, i, j, s)), 255) + sum.ec_p1_add_or_double(sum, prod) - blst_p1_add_or_double(result, hashNameI(t.name, i), sum) - result.blst_p1_mult(result, ssk.key, 255) + ec_p1_add_or_double(result, hashNameI(t.name, i), sum) + result.ec_p1_mult(result, ssk.key, 255) -proc generateAuthenticatorOpt(i: int64, s: int64, t: TauZero, ubase: openArray[blst_scalar], f: File, ssk: SecretKey): blst_p1 = +proc generateAuthenticatorOpt(i: int64, s: int64, t: TauZero, ubase: openArray[ec_scalar], f: File, ssk: SecretKey): ec_p1 = ## Optimized implementation of authenticator generation ## This implementation is reduces the number of scalar multiplications ## from s+1 to 1+1 , using knowledge about the scalars (r_j) @@ -229,31 +230,31 @@ proc generateAuthenticatorOpt(i: int64, s: int64, t: TauZero, ubase: openArray[b ## ## With the paper's multiplicative notation, we use: ## (H(file||i)\cdot g^{\sum{j=0}^{s-1}{r_j \cdot m[i][j]}})^{\alpha} - var sum: blst_fr - var sums: blst_scalar + var sum: ec_fr + var sums: ec_scalar for j in 0 ..< s: - var a, b, x: blst_fr - a.blst_fr_from_scalar(ubase[j]) - b.blst_fr_from_scalar(fromBytesBE(getSector(f, i, j, s))) - x.blst_fr_mul(a, b) - sum.blst_fr_add(sum, x) - sums.blst_scalar_from_fr(sum) + var a, b, x: ec_fr + a.ec_fr_from_scalar(ubase[j]) + b.ec_fr_from_scalar(fromBytesBE(getSector(f, i, j, s))) + x.ec_fr_mul(a, b) + sum.ec_fr_add(sum, x) + sums.ec_scalar_from_fr(sum) - result.blst_p1_from_affine(BLS12_381_G1) - result.blst_p1_mult(result, sums, 255) + result.ec_p1_from_affine(EC_G1) + result.ec_p1_mult(result, sums, 255) - result.blst_p1_add_or_double(result, hashNameI(t.name, i)) - result.blst_p1_mult(result, ssk.key, 255) + result.ec_p1_add_or_double(result, hashNameI(t.name, i)) + result.ec_p1_mult(result, ssk.key, 255) -proc generateAuthenticator(i: int64, s: int64, t: TauZero, ubase: openArray[blst_scalar], f: File, ssk: SecretKey): blst_p1 = +proc generateAuthenticator(i: int64, s: int64, t: TauZero, ubase: openArray[ec_scalar], f: File, ssk: SecretKey): ec_p1 = ## Wrapper to select tag generator implementation # let a = generateAuthenticatorNaive(i, s, t, f, ssk) let b = generateAuthenticatorOpt(i, s, t, ubase, f, ssk) - # doAssert(a.blst_p1_is_equal(b).bool) + # doAssert(a.ec_p1_is_equal(b).bool) return b -proc setup*(ssk: SecretKey, s:int64, filename: string): (Tau, seq[blst_p1]) = +proc setup*(ssk: SecretKey, s:int64, filename: string): (Tau, seq[ec_p1]) = ## Set up the POR scheme by generating tags and metadata let file = open(filename) let n = split(file, s) @@ -264,22 +265,22 @@ proc setup*(ssk: SecretKey, s:int64, filename: string): (Tau, seq[blst_p1]) = t.name[i] = byte Rng.instance.rand(0xFF) # generate the coefficient vector for combining sectors of a block: U - var ubase: seq[blst_scalar] + var ubase: seq[ec_scalar] for i in 0 ..< s : var - u: blst_p1 - ub: blst_scalar + u: ec_p1 + ub: ec_scalar rndP1(u, ub) t.u.add(u) ubase.add(ub) #TODO: a better bytearray conversion of TauZero for the signature might be needed # the current conversion using $t might be architecture dependent and not unique - let signature = sign(ssk.signkey, $t) - let tau = Tau(t: t, signature: signature.exportRaw()) + let signature = ec_sign(ssk.signkey, $t) + let tau = Tau(t: t, signature: signature.ec_export_raw()) #generate sigmas - var sigmas: seq[blst_p1] + var sigmas: seq[ec_p1] for i in 0 ..< n : sigmas.add(generateAuthenticator(i, s, t, ubase, file, ssk)) @@ -296,106 +297,62 @@ proc generateQuery*(tau: Tau, spk: PublicKey, l: int): seq[QElement] = q.V.rndScalar() #TODO: fix range result.add(q) -proc generateProof*(q: openArray[QElement], authenticators: openArray[blst_p1], spk: PublicKey, s: int64, filename: string): (seq[blst_scalar], blst_p1) = +proc generateProof*(q: openArray[QElement], authenticators: openArray[ec_p1], spk: PublicKey, s: int64, filename: string): (seq[ec_scalar], ec_p1) = ## Generata BLS proofs for a given query let file = open(filename) - var mu: seq[blst_scalar] + var mu: seq[ec_scalar] for j in 0 ..< s : - var muj: blst_fr + var muj: ec_fr for qelem in q : - var x, v, sector: blst_fr + var x, v, sector: ec_fr let sect = fromBytesBE(getSector(file, qelem.I, j, s)) - sector.blst_fr_from_scalar(sect) - v.blst_fr_from_scalar(qelem.V) - x.blst_fr_mul(v, sector) - muj.blst_fr_add(muj, x) - var mujs: blst_scalar - mujs.blst_scalar_from_fr(muj) + sector.ec_fr_from_scalar(sect) + v.ec_fr_from_scalar(qelem.V) + x.ec_fr_mul(v, sector) + muj.ec_fr_add(muj, x) + var mujs: ec_scalar + mujs.ec_scalar_from_fr(muj) mu.add(mujs) - var sigma: blst_p1 + var sigma: ec_p1 for qelem in q: - var prod: blst_p1 - prod.blst_p1_mult(authenticators[qelem.I], qelem.V, 255) - sigma.blst_p1_add_or_double(sigma, prod) + var prod: ec_p1 + prod.ec_p1_mult(authenticators[qelem.I], qelem.V, 255) + sigma.ec_p1_add_or_double(sigma, prod) file.close() return (mu, sigma) -proc pairing(a: blst_p1, b: blst_p2): blst_fp12 = - ## Calculate pairing G_1,G_2 -> G_T - var aa: blst_p1_affine - var bb: blst_p2_affine - blst_p1_to_affine(aa, a) - blst_p2_to_affine(bb, b) - var l: blst_fp12 - blst_miller_loop(l, bb, aa) - blst_final_exp(result, l) - -proc verifyPairingsNaive(a1: blst_p1, a2: blst_p2, b1: blst_p1, b2: blst_p2) : bool = - let e1 = pairing(a1, a2) - let e2 = pairing(b1, b2) - return e1 == e2 - -proc verifyPairingsNeg(a1: blst_p1, a2: blst_p2, b1: blst_p1, b2: blst_p2) : bool = - ## Faster pairing verification using 2 miller loops but ony one final exponentiation - ## based on https://github.com/benjaminion/c-kzg/blob/main/src/bls12_381.c - var - loop0, loop1, gt_point: blst_fp12 - aa1, bb1: blst_p1_affine - aa2, bb2: blst_p2_affine - - var a1neg = a1 - blst_p1_cneg(a1neg, 1) - - blst_p1_to_affine(aa1, a1neg) - blst_p1_to_affine(bb1, b1) - blst_p2_to_affine(aa2, a2) - blst_p2_to_affine(bb2, b2) - - blst_miller_loop(loop0, aa2, aa1) - blst_miller_loop(loop1, bb2, bb1) - - blst_fp12_mul(gt_point, loop0, loop1) - blst_final_exp(gt_point, gt_point) - - return blst_fp12_is_one(gt_point).bool - -proc verifyPairings(a1: blst_p1, a2: blst_p2, b1: blst_p1, b2: blst_p2) : bool = - ## Wrapper to select verify pairings implementation - verifyPairingsNaive(a1, a2, b1, b2) - #verifyPairingsNeg(a1, a2, b1, b2) - -proc verifyProof*(tau: Tau, q: openArray[QElement], mus: openArray[blst_scalar], sigma: blst_p1, spk: PublicKey): bool = +proc verifyProof*(tau: Tau, q: openArray[QElement], mus: openArray[ec_scalar], sigma: ec_p1, spk: PublicKey): bool = ## Verify a BLS proof given a query # verify signature on Tau - var signature: Signature - if not signature.fromBytes(tau.signature): + var signature: ec_signature + if not signature.ec_from_bytes(tau.signature): return false - if not verify(spk.signkey, $tau.t, signature): + if not ec_verify(spk.signkey, $tau.t, signature): return false - var first: blst_p1 + var first: ec_p1 for qelem in q : - var prod: blst_p1 - prod.blst_p1_mult(hashNameI(tau.t.name, qelem.I), qelem.V, 255) - first.blst_p1_add_or_double(first, prod) - doAssert(blst_p1_on_curve(first).bool) + var prod: ec_p1 + prod.ec_p1_mult(hashNameI(tau.t.name, qelem.I), qelem.V, 255) + first.ec_p1_add_or_double(first, prod) + doAssert(ec_p1_on_curve(first).bool) let us = tau.t.u - var second: blst_p1 + var second: ec_p1 for j in 0 ..< len(us) : - var prod: blst_p1 - prod.blst_p1_mult(us[j], mus[j], 255) - second.blst_p1_add_or_double(second, prod) - doAssert(blst_p1_on_curve(second).bool) + var prod: ec_p1 + prod.ec_p1_mult(us[j], mus[j], 255) + second.ec_p1_add_or_double(second, prod) + doAssert(ec_p1_on_curve(second).bool) - var sum: blst_p1 - sum.blst_p1_add_or_double(first, second) + var sum: ec_p1 + sum.ec_p1_add_or_double(first, second) - var g{.noInit.}: blst_p2 - g.blst_p2_from_affine(BLS12_381_G2) + var g{.noInit.}: ec_p2 + g.ec_p2_from_affine(EC_G2) return verifyPairings(sum, spk.key, sigma, g)