Initially, I based this code off schnorrkel, which provides sign-verify methods for many usage patterns.
Ideally, schnorrkel should encapsulate the VRFOutput inside a more user-friendly VRFSignature type, but does not do so. I believed the vrfs_merge tricks mattered so I exposed it even for simple use cases, but.. Actually usages never definitively materialized even in AnV so we'll consider this choice a mistake for the user-friendly interface. We thus could encapsulate VRFOutput inside a more user-friendly VRFSignature type.
We need a VRFInOut based approach for the experts interface regardless, which then support vrfs_merge and others. I'd begun twisting ring-vrf towards using VRFInOut for the "most user-friendly" interface too. We could keep this path, rip out the current "inconvenience" methods, and plan to write actual convenience methods later.
How should we handle malleability?
We could ignore malleability except for writing big warnings all over, but doing so kinda kills any user friendlyness. In schnorrkel, I forced the non-malleabile version for most use cases by using traits to simulate defaults, but this looks impossible now.
We should probably keep the current new_* methods for the experts interface, but then force the minimal malleability inside the users friendly interface. I held off doing this because then outputs of ring vrf and non-ring vrfs differ, but oh well..
As an alternative, we could provide a malleability: MalleabilityMode type that propagates from more user-friendly layers, but this looks unnecessarily complex.
How do we handle public key blinding?
We should enforce that blinding factors only get used once, which seemingly requires creating them inside vrf_sign methods. We could make them an argument to dleq_prove, but not sure if doing this benefits anyone.
Do we need batchable schnorr proofs provided by NewChallengeOrWitness?
There is a simple version of the DLEQ proof given by
In = H_1(msg)
Out = sk In
Non-anonymous VRF:
R := r (G + In)
c := H(In, Out, PK, R)
s := c sk + r
We verify by recomputing c and checking
s (G + In) = c (PK + Out) + R
which looks batchable.
Anonymized Ring VRF:
APK := PK + b_pk B
R' := r (G + In) - b_R B
c := H(In, Out, PK, R')
delta := c b_pk + b_R
s := c sk + r
We again verify by recomputing c and checking
s (G + In) = c (PK + Out) + R'
which again looks batchable.
SNARK { APK : exists scalar b_pk and copath rho from root to PK = APK - b_pk B }
This speeds things up a tiny bit and avoids this complexity.
Initially, I based this code off schnorrkel, which provides sign-verify methods for many usage patterns.
Ideally, schnorrkel should encapsulate the
VRFOutput
inside a more user-friendlyVRFSignature
type, but does not do so. I believed thevrfs_merge
tricks mattered so I exposed it even for simple use cases, but.. Actually usages never definitively materialized even in AnV so we'll consider this choice a mistake for the user-friendly interface. We thus could encapsulateVRFOutput
inside a more user-friendlyVRFSignature
type.We need a
VRFInOut
based approach for the experts interface regardless, which then supportvrfs_merge
and others. I'd begun twisting ring-vrf towards usingVRFInOut
for the "most user-friendly" interface too. We could keep this path, rip out the current "inconvenience" methods, and plan to write actual convenience methods later.We could ignore malleability except for writing big warnings all over, but doing so kinda kills any user friendlyness. In schnorrkel, I forced the non-malleabile version for most use cases by using traits to simulate defaults, but this looks impossible now.
We should probably keep the current
new_*
methods for the experts interface, but then force the minimal malleability inside the users friendly interface. I held off doing this because then outputs of ring vrf and non-ring vrfs differ, but oh well..As an alternative, we could provide a
malleability: MalleabilityMode
type that propagates from more user-friendly layers, but this looks unnecessarily complex.We should enforce that blinding factors only get used once, which seemingly requires creating them inside
vrf_sign
methods. We could make them an argument todleq_prove
, but not sure if doing this benefits anyone.NewChallengeOrWitness
?There is a simple version of the DLEQ proof given by
In = H_1(msg) Out = sk In
Non-anonymous VRF: R := r (G + In) c := H(In, Out, PK, R) s := c sk + r
We verify by recomputing c and checking s (G + In) = c (PK + Out) + R which looks batchable.
Anonymized Ring VRF: APK := PK + b_pk B R' := r (G + In) - b_R B c := H(In, Out, PK, R') delta := c b_pk + b_R s := c sk + r
We again verify by recomputing c and checking s (G + In) = c (PK + Out) + R' which again looks batchable.
SNARK { APK : exists scalar b_pk and copath rho from root to PK = APK - b_pk B }
This speeds things up a tiny bit and avoids this complexity.