-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deterministic Pedersen secret blinding factor generation #24
Conversation
737ca2c
to
d6527c4
Compare
src/pedersen.rs
Outdated
// TODO:is this ok? | ||
let cb = S::challenge(&[&input.0, &output.0], ad.as_ref()); | ||
let b = self.scalar * cb; | ||
|
||
// Construct the nonces | ||
let k = S::nonce(&self.scalar, input); | ||
let kb = S::nonce(&k, input); | ||
let b = S::nonce(&kb, input); | ||
let kb = S::nonce(&b, input); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@drskalman I'd like your opinion on this method of generating the blinding factor (blinding
).
In practice:
let cb = challenge([input_point, output_point], aux_data);
let blinding = secret_scalar * cb;
// Nonces:
let k = nonce(secret_scalar, input);
let kb = nonce(blinding, input);
I'd like to make it deterministic, so I've used everything I have as input, plus the secret. Everything hashed together somehow (I'm using the challenge function)
The nonces are constructed as defined in the spec here:
My only doubt is about the blinding factor construction.
Is the way I'm constructing it fine?
challenge
and nonce
functions I'm using are defined here (both also referenced in our bandersnatch spec)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note that I've changed how kb
is computed
src/pedersen.rs
Outdated
@@ -49,10 +49,13 @@ impl<S: PedersenSuite> Prover<S> for Secret<S> { | |||
output: Output<S>, | |||
ad: impl AsRef<[u8]>, | |||
) -> (Proof<S>, ScalarField<S>) { | |||
// TODO:is this ok? | |||
let cb = S::challenge(&[&input.0, &output.0], ad.as_ref()); | |||
let b = self.scalar * cb; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you multiply here? Why do you need the algebraic structure? if b
is supposed to completely [pseudo]random then why not do the nonce(scalar, cb)
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need any algebraic relation. I just tried to mix the thing up with the secret scalar as well.
The nonce
function takes two params: 1st a scalar, 2nd a point.
cb
is a scalar as well, so I can't use it as second param to nonce
. So I ended up just multiplying the two.
Maybe the challenge
output is already sufficient? As it also mixes up the output
which in fact is obtained using the secret
(i.e. output = input*secret
).
To recap, I'd like to use what I have at the begin of Pedersen prove
function to deterministically generate a pseudo random secret blinding factor b
.
At entry point, this is what we have available:
input
pointoutput
point which in practice isinput * secret
ad
(additional data) bytessecret
scalar
Functions we can use:
challenge
function, which takes a sequence of points and a byte array and returns a scalarnonce
function, which takes a scalar and a point and returns a scalar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hey @elizabeth-crites, can I have your opinion please ? Ty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think challenge
only is enough, first I don't know if either input and output are secret (I should but I haven't checked) plus, while challenge should be unpredictable, I don't think by definition it needs to be hiding.
Moreover, challenges are supposed to be publicaly computable so using only challenge with secret input results in a confusing code.
The function to generate deterministic "randomness" in RFC 8032 gets both inputs as bytes, so you could potentially use the definition in RFC 8032 (without involving the point on the curve -> byte steps).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another point @elizabeth-crites brought up is that if you make the process deterministics then an attacker might be able to force the same input and then you end up with the same output so they can find out who has signed the second time. So my guess is that we want this VRF to stay anonymous even if the attacker plays the same input multiple time.
In this way it seems that making b
deterministic fundamentally breaks the anonynmity property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah using only the challenge was absolutely incorrect, indeed I've updated the thing to also plug the secret scalar into the computation.
For what concerns the anonymity considerations, you're right I'll make it deterministic only for test-vectors generation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought a bit more about this. The VRF signature contains the VRF output which is always k*I
(with k
the secret scalar and I
the vrf input point). Thus the anonymity reasoning thing doesn't here. I.e. you can still link the signer of two identical inputs (as the vrf output is the same regardless of the blinding factor randomness).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW I've finally decided to let the user pass the blinding factor. So its not the library responsibility to construct it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you are right, maintaining annonymity for the same replayed input then is not part of the threat model.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW I've finally decided to let the user pass the blinding factor. So its not the library responsibility to construct it
I'd speculate that would make some people who don't have faith in user (of the library)'s ability in generating a sufficient random number uncomfortable. But it does simlifies the Spec. You don't generate the secret key for the user either, so that should be OK. Plus I see you are offering the default to generate deterministic psudo-randomness.
src/pedersen.rs
Outdated
/// Pedersen blinding factor. | ||
/// | ||
/// Default implementation is deterministic and inspired by the RFC-9381 challenge procedure. | ||
fn blinding( | ||
secret: ScalarField<Self>, | ||
pts: &[&AffinePoint<Self>], | ||
ad: &[u8], | ||
) -> ScalarField<Self> { | ||
const DOM_SEP_START: u8 = 0xC2; | ||
const DOM_SEP_END: u8 = 0x00; | ||
let mut buf = [Self::SUITE_ID, &[DOM_SEP_START]].concat(); | ||
pts.iter().for_each(|p| { | ||
Self::Codec::point_encode(p, &mut buf); | ||
}); | ||
buf.extend_from_slice(ad); | ||
buf.push(DOM_SEP_END); | ||
let hash = &utils::hash::<Self::Hasher>(&buf); | ||
let c = ScalarField::<Self>::from_be_bytes_mod_order(hash); | ||
secret * c | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrapped up the procedure here. Is a "default" and the user can eventually overwrite it.
(but we also have to consider that most of the folks will stick to the defaults).
The procedure is inspired by the RFC-9831 challenge to mix up all the available inputs we have when prove is called (see below). And then we finally multiply the resulting scalar by the secret key scalar.
If this is not considered a good default then I have to modify the interface to take the blinding factor from the user, because I don't think we can do a lot better with what we have
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AlistairStewart thinks multiplying secret key with the transcript point is OK. I still would prefer a non-algebraic way of mixing the secret key (like they generate deterministic randmoness in eddsa) when you don't need the algebraic structure at all.
No description provided.