# VRF & ASM ## VRF - [verifiable random function for random sortitioning](https://hackmd.io/@jkrvivian/H16hUsIEj) - [medium on algorand](https://medium.com/algorand/algorand-releases-first-open-source-code-of-verifiable-random-function-93c2960abd61) - [ietf draft VRF](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-15#section-5.1) - [algorand impl](https://github.com/algorand/go-algorand/blob/master/crypto/vrf.go) - BLS signature for VRF? ### [VRF RFC draft-15](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-vrf-15#section-5) #### API VRF=(Gen,Hash,Prove,Verify) VRF.Gen() -> (sk, pk) VRF.Prove(sk, alpha) -> prf VRF.Hash(prf) -> rnd -- deterministic in sk, alpha? VRF.Verify(pk, alpha, prf) -> 2 #### Security Properties ##### Uniqueness `forall pk, alpha exists! prf: VRF.Verify(pk, alpha, prf) = 1` `hard to find pk, alpha, (prf1, prf2: VRF.Verify(pk, alpha, prfi) = 1) such that VRF.Hash(prf1) != VRF.Hash(prf2)` - full: Adv is allowed to choose any (bad, low entropy) pk - trusted: same as full, but Adv can't choose a bad pk (ie. Adv knows sk and pk=VRF.Gen(sk)) ##### Collision resistance `forall pk, alpha1, alpha2 => VRF.Hash(VRF.Prove(pk, alpha1)) != VRF.Hash(VRF.Prove(pk, alpha2))` `hard to find pk, (alpha1, alpha2, prf1, prf2: VRF.Verify(pk, alphai, prfi) = 1) such that VRF.Hash(prf1) == VRF.Hash(prf2)` - full: Adv is allowed to choose any (bad, low entropy) pk - trusted collision resistance: same as full, but Adv can't choose pk (ie. Adv knows sk and pk=VRF.Gen(sk)) ##### Pseudorandomness If sk and prf are unknown then hash=VRF.Hash(prf) is indistinguishable from random for any alpha - full: Adv receive pk, choose alphai and observe prfi, choose alpha - selective: Adv choose alpha, receive pk, choose alphai, observe prfi ##### Unpredictability under malicious key gen Similar to pseudorandomness property but with "good-enough" key gen (ie. with enough entropy) #### ECVRF construction Two variants: with or without key validation (KV). ECVRF(sk, alpha) -> rnd 1. DlEq{(G, sk G, KDF(alpha), sk KDF(alpha); sk): sk G = sk KDF(alpha)} 2. rnd = Hash(sk KDF(alpha)) Properties (mostly follow from properties of ZKP, Hash, KDF): - full uniqueness (perfect binding, onewayness of KDF) - trusted/full(KV) collision resistance (collision resistance of KDF, Hash) - full pseudorandomness (hash pseudorandomness) - unpredictability(KV) (hash unpredict) ECVRF.Gen(sk) -> pk 1. return pk = KDF(sk)*G ECVRF.Prove(sk, alpha, [salt]) -> prf 1. x = KDF(sk); Y = x G 2. H = encode_to_curve(salt, alpha) 3. h = point_to_string(H) 4. X = x H 5. k = nonce_gen(sk, h) 6. c = challenge_gen(Y, H, X, kG, kH) 7. s = k + cx 8. return prf = (X, c, s) ECVRF.Hash(prf) -> Option<rnd> 1. (X, c, s) = decode_prf(prf)? 2. return Some rnd = H(suite_string | dom_sep(0x03) | point_to_string(cofactor * X) | dom_sep(0x00)) ECVRF.Verify(pk, alpha, prf, [salt], KV) -> 2 // KV: 2; validate keys or not 1. Y = string_to_point(pk)? 2. if KV then validate_key(Y)? 3. (X, c, s) = decode_prf(prf)? 4. H = encode_to_curve(salt, alpha) 5. U = sG-cY 6. V = sH-cX 7. c ?= challenge_gen(Y, H, X, U, V) 8. return true ECVRF.encode_to_curve(salt, alpha) -> H -- hash to curve ECVRF.nonce_gen(sk, h) -> k -- deterministic gen nonce scalar ECVRF.challenge_gen(Y, H, X, U, V) -> c -- transcript/hash ECVRF.decode_prf(prf) -> Option<X, c, s> -- parse/validate PK and scalars ECVRF.validate_key(Y) -> 2 -- for curve25519 there's a list of 8 bad points, so use lookup instead of `cofactor*` 1. return cofactor*Y != O ##### Parameters TODO: - RFC ciphersuites - salt - Hash - KDF - encode_to_curve - nonce_gen - challenge_gen - dom_sep - KV - E, G, cofactor / ristretto - string_to_point/point_to_string / Elligator ### BLS BLS.KeyGen(sk) -> pk = sk G_2 BLS.Sign(sk; m) -> sig = sk H(m) BLS.Verify(pk; m) -> 2 = e(H(m), pk) =? e(sig, G_2) BLS.VRF(sk; alpha) -> rnd = H(sk H(alpha)) ### BLS-dl BLS is essentially DlEq proof. Schnorr DlEq: BLS-dl.KeyGen(sk) -> pk = sk G BLS-dl.Sign(sk; m) -> sig 1. r <-R Z_q, R = r G, X = r H(m), T = sk H(m) 2. c = H(pk, R, X, m) 3. s = r + c sk 4. sig = (s,c,T) or (s, R, X, T) BLS-dl.Verify(pk; m, (s,R,X,T)) -> 2 1. c = H(pk, R, X, m) 2. s G ?= R + c pk 3. s H(m) ?= X + c T BLS-dl.Verify(pk; m, (s,c,T)) -> 2 1. R = s G - c pk 2. X = s H(m) - c T 3. c ?= H(pk, R, X, m) ### Implementations - r2ishiguro: ECVRF with ed25519; uses random nonce - coniks-go: ECVRF with ed25519; uses det nonce, let efficient, doesn't use mixed scalar mul - algorand with libsodium fork (branch draft-irtf-cfrg-vrf-03): ECVRF with ed25519 - Algorand, Ouroboros-Praos and Elrond; Harmony; HERB (see [Galindo et al](https://eprint.iacr.org/2020/096.pdf) for references). ### Distributed VRF Pairing allows to verify DlEq without an explicit proof. Schnorr requires an explicit proof for DlEq. Standard way for DVRF: 1. DKG - needs DlEq for partial secret sig verification 2. DH: sk KDF(alpha) - needs DlEq to prove DH vs pk [Galindo et al](https://eprint.iacr.org/2020/096.pdf): - Dfinity-DVRF - DKG (Schnorr/DlEq) + Gen (pairing-based/BLS) - DDH-DVRF - DKG (Schnorr/DlEq) + Gen (Schnorr/DlEq) - GLOW-DVRF - DKG (pairing-based) + Gen (Schnorr/DlEq) ### Distributed Random Beacon DRB vs VRF: - DRB has state that changes with time after each new beacon is generated - DRB has state transition given current beacon and proof - state_n = beacon_n || n ## Accountable subgroup multisignature (ASM) ### [Boneh, Drijvers, Neven](https://eprint.iacr.org/2018/483.pdf) BLS.Sign(sk,m) = sk H(m) BLS.Verify(pk,sig,m) = e(H(m),pk) ?= e(sig,G) aggregation: {sig_i} => sig=\sum_i sig_i verify: e(sig,G) ?= \sum_i e(H(m_i),pk_i) rogue-key attack (with m_1=m_2): pk_2(a) = aG - pk_1 defense: Prove KoSK, use different m_1!=m_2 Constructions: MSP, MSP-pop, AMSP, AMSP-pop, ASM, MSDL, MSDL-pop #### MSP - BLS-based multisig MSP.KeyGen(sk_i) -> pk_i = sk_i G_2 MSP.Agg({pk_i}) -> A_i = H(pk_i,{pk_j}) MSP.KeyAgg({pk_i}) -> apk = \sum_i A_i pk_i MSP.Sign({pk_i}, sk_i, m) -> sig_i = (sk_i A_i) H(m) MSP.SigAgg({sig_i}) -> sig = \sum_i sig_i MSP.Verify(apk, m, sig) -> 2 = e(sig,G_2) ?= e(H(m), apk) MSP.BatchVerify ... #### MSP-pop Proof-of-possession variant of MSP. Simpler KeyAgg, but instead public keys are shared with PoP: MSP-pop.KeyGen(sk_i) -> (pk_i, prf_i) = (sk_i G_2, sk_i H(pk_i)) MSP-pop.Agg({pk_i}) -> A_i = 1 MSP-KeyAgg({pk_i}) -> apk = \sum_i pk_i ... #### AMSP - aggregate multi-signature scheme AMSP := MSP AMSP.Sign({pk_i}, sk_i, m) -> sig_i = (sk_i A_i) H(*apk*, m) AMSP.Verify ... AMSP.BatchVerify ... #### AMSP-pop Similar to MSP-pop. #### ASM - accountable subset multisig ASM.KeyGen := MSP.KeyGen ASM.KeyAgg := MSP.KeyAgg ASM.GroupSetup(sk_i, {pk_i}) -> mk_i 1. for all j: send to j mk_{j,i}=(sk_i A_i) H(apk, j) 2. mk_i = \sum_j mk_{i,j} // mk_i = AMSP.Sign(i) ASM.Sign({pk_i}, sk_i, mk_i, m) -> sig_i = mk_i + sk_i H(apk, m_i) ASM.SigAgg({pk_i}, S, {sig_j}) -> (pk, sig) 1. pk = \sum_{j in S} pk_i 2. sig = \sum_{j in S} sig_j ASM.Verify(apk, S, m, (pk, sig)) -> 2 = e(H(apk,m),pk)*e(\sum_{j in S} H(apk, j), apk) ?= e(sig, G_2) #### MSDL - Schnorr-based multisig MSDL.KeyGen = MSP.KeyGen MSDL.Agg := MSP.Agg MSDL.KeyAgg := MSP.KeyAgg MSDL.Sign({pk_i}, sk_i, m) -> sig 1. r_i <-R Z_q, R_i = r_i G, broadcast t_i = H(R_i) 2. broadcast R_i, verify t_j ?= H(R_j) 3. R = \sum_j R_j, c = H(R, apk, m), broadcast s_i = r_i + c sk_i A_i 4. s = \sum_j s_j, sig = (R, s) MSDL.Verify ... #### MSDL-pop Extra round in KeyGen? ### [Agirtas, Yayla](https://eprint.iacr.org/2022/018.pdf) ASM Schemes #### vASM - ASM with VSS-verifiable subgroup setup Feldman VSS + PoP, subgroup unknown before signing vASM.KeyGen(sk_i) -> pk_i = sk_i G_2 vASM.GroupSetup({pk_i}, S={1..n}, sk_i) -> (mk_i, C) 1. f_i(x) = \sum_j a_{j,i} x^i <-R Z*_q[x], deg(f_i)=n-1, f_i(0) = sk_i, (coeffs are non-zero) 2. C_i = {C_{j,i} = a_{j,i} G_2} 3. for all j send to j (f_i(j),C_i) 4. mk_i = \sum_{j in S} f_j(i), C = \sum_j C_j 5. C_0 ?= \sum_{j in S} pk_j; mk_i G_2 ?= \sum_k i^k C_k 6. return mk_i, C vASM.Sign(mk_i, m) -> sig_i = mk_i H(m) // send to combiner vASM.SigAgg(S, {sig_i}) -> sig = \sum_{i in S} sig_i vASM.Verify(C, S, m, sig) -> 2 = e(sig, G_2) ?= e(H(m), \sum_k (\sum_{i in S} i^k) C_k) #### vASM-dl - DL/Schnorr-based variant of vASM vASM.Verify(C, S, m, sig) does DlEq verification: Dl_{H(m)}(sig) ?= Dl_{G_2}(\sum_k (\sum_{i in S} i^k) C_k) Let's make it explicit with Schnorr: vASM-dl.KeyGen vASM-dl.GroupSetup -> (mk_i, C) // Feldman DKG, mk_i G = f(C) -- public key reconstructed from commitments C vASM-dl.Sign(mk_i, r_i, m) -> sig_i 1. T = mk_i H(m) // vASM.Sign 2. R = r_i G, X = r_i H(m) 3. c_i = H(apk, R, X, T, m) // c_i must be const => R_i=const, X_i=const, T_i=const 4. s_i = r_i + c_i mk_i 5. return sig_i = (s_i, R, X, T) vASM-dl.SigAgg(S, {sig_i}) -> sig = \sum_{i in S} #### ASMwSA - ASM with subgroup authentication Explicit Subgroup ASMwSA.KeyGen := vASM.KeyGen ASMwSA.Agg({pk_i}) -> A_i = H(pk_i, {pk_i}) ASMwSA.KeyAgg({pk_i}) -> apk = \sum_i A_i pk_i ASMwSA.GroupSetup := ASM.GroupSetup? ASMwSA.GroupSetup({pk_i}) -> m_{i,j} = (A_i sk_i) H(apk, j) ASMwSA.Sign({m_{i,j}}, S, m, sk_i) -> sig_i 1. mk_i = \sum_{j in S} m_{i,j} 2. sig_i = mk_i + (A_i sk_i) H(apk, m) ASMwSA.SigAgg({pk_i}, S, {sig_i}) -> (spk, sig) = (\sum_{i in S} A_i pk_i, \sum_{i in S} sig_i) ASMwSA.Verify(apk, (spk, sig), S, m) -> 2 = e(sig, G_2) ?= e(H(apk,m) + \sum_{j in S} H(apk, j), spk) #### ASMwCA - ASM with combiner authentication Designated combiner, subgroup unknown before signing ASMwCA.KeyGen := ASMwSA.KeyGen ASMwCA.Agg := ASMwSA.Agg ASMwCA.KeyAgg := ASMwSA.KeyAgg ASMwCA.GroupSetup := ASMwSA.GroupSetup ASMwCA.Sign({m_{i,j}}, S, m, sk_i) -> sig_i = m_{i,i} + (A_i sk_i) H(apk, m) ASMwCA.SigAgg({m_{i,j}}, {pk_i}, S, {sig_i}) -> (spk, sig) = (\sum_{i in S} A_i pk_i, \sum_{i in S} (sig_i + \sum_{j in S} m_{i,j})) ASMwCA.Verify(apk, (spk, sig), S, m) -> 2 = e(sig, G_2) ?= e(H(apk,m) + \sum_{j in S} H(apk, j), spk) #### Aggregated ASM