Skip to content
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

Merged
merged 11 commits into from
Jul 26, 2024

Conversation

davxy
Copy link
Owner

@davxy davxy commented Jul 9, 2024

No description provided.

src/pedersen.rs Outdated
Comment on lines 52 to 58
// 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);
Copy link
Owner Author

@davxy davxy Jul 10, 2024

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:

https://github.com/davxy/bandersnatch-vrfs-spec/blob/a66a07c3d0bb7605c7272a791359cca2f4fa1a92/specification.md?plain=1#L251-L252

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)

Copy link
Owner Author

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

@davxy davxy marked this pull request as ready for review July 10, 2024 12:40
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;

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?

Copy link
Owner Author

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:

  1. input point
  2. output point which in practice is input * secret
  3. ad (additional data) bytes
  4. secret scalar

Functions we can use:

  1. challenge function, which takes a sequence of points and a byte array and returns a scalar
  2. nonce function, which takes a scalar and a point and returns a scalar

Copy link
Owner Author

@davxy davxy Jul 25, 2024

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

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).

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.

Copy link
Owner Author

@davxy davxy Jul 25, 2024

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

Copy link
Owner Author

@davxy davxy Jul 26, 2024

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).

Copy link
Owner Author

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

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.

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.

@davxy davxy changed the title Deterministic Pedersen secret generation Deterministic Pedersen secret blinding factor generation Jul 25, 2024
src/pedersen.rs Outdated
Comment on lines 7 to 26
/// 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
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drskalman @elizabeth-crites

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

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.

@davxy davxy merged commit 849c1d4 into main Jul 26, 2024
5 checks passed
@davxy davxy deleted the pedersen-secret-gen branch July 26, 2024 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants