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

Adding modulo field prime to scalar.new #94

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

logicalmechanism
Copy link
Contributor

@logicalmechanism logicalmechanism commented Sep 16, 2024

When using the Fiat-Shamir heuristic with the available in crypto hash functions it is possible to get values larger than the field prime of bls12-381. I have two options, truncating the hash at my function level or I add in a modulo the field prime term for values larger than the field prime.

I opt for modulo the field prime.

Example:

pub fn fiat_shamir_heuristic(
  // compressed g element
  g_b: ByteArray,
  // compressed g^r element
  g_r_b: ByteArray,
  // compressed g^x element
  u_b: ByteArray,
  // upper bound as bytearray
  b: ByteArray,
) -> ByteArray {
  // concat g_b, g_r_b, u_b, and b together then hash the result
  // blake2b_256 is the cheapest on chain
  g_b
    |> bytearray.concat(g_r_b)
    |> bytearray.concat(u_b)
    |> bytearray.concat(b)
    |> crypto.blake2b_256()
}
test real_fiat_shamir_transform() {
  fiat_shamir_heuristic(
    #"97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb",
    #"81b223cea171a87feba9b7749a2df7601c5a75ae01155fadc124a2ac49099a514cf1e7d9cdc769dceab14a95bd6cb0bd",
    #"a09d99e02f7200526dc55ef722cc171e7aa14fc732614c02ac58d59d7026a7eb18d8798f6928ea2b513f3a4feb0c94d1",
    #"acab",
  ) == #"8f9409d05727322c9f2d1d0adf817b8d0e3a681977edc9ab866879a4a4f8f5b9"
}

The resulting challenge value from the Fiat-Shamir transform will crash at validation, due to an expect erroring because scalar.new is a scaler if and only if it is in range, thus preventing spending when performing my NIZK.

If we just added a modulo field prime for values larger than the field prime then this never happens.

Also about 54% of blake2b_256 hashes will be larger than the field prime so it probably a good idea to account for this.

@logicalmechanism
Copy link
Contributor Author

logicalmechanism commented Sep 16, 2024

Just realized my lock file was added. Sorry about that. Also my formatting is all off. I am just gonna leave as it can be fixed later.

@perturbing
Copy link
Contributor

perturbing commented Sep 17, 2024

@logicalmechanism, please note that you can use the blake224 to map into the field (it's the same cost as blake256 onchain), 224 bits are enough entropy.

I think it is valuable to have the function fail on input that is not correct. Take for example the plonk protocol, that requires public inputs to be field elements.

@logicalmechanism
Copy link
Contributor Author

logicalmechanism commented Sep 17, 2024

@logicalmechanism, please note that you can use the blake224 to map into the field (it's the same cost as blake256 onchain), 224 bits are enough entropy.

I think it is valuable to have the function fail on input that is not correct. Take for example the plonk protocol, that requires public inputs to be field elements.

Forcing users to adopt a specific hash function, such as blake224, doesn't seem optimal, especially when a simple modulo operation can address the issue without restricting flexibility. By using blake224, we are discarding 99.999% of possible hash outputs above its maximum value, which seems like a missed opportunity in terms of utilizing the available entropy.

Allowing users to choose any hash function and applying a modulo operation to fit within the field prime feels more inclusive and adaptable. Additionally, it enhances security by enabling stronger or more specialized hash functions without requiring fundamental changes to the protocol. Flexibility in cryptographic primitives can future-proof the system while maintaining robustness.

It feels more open to allow any hash to be used and it does improve security thus adding the modulo the field prime for large values makes sense here.

test cheapest_hash() {
  // PASS [mem:    805, cpu:    2467639] expensive hash
  // crypto.keccak_256(#"") == #"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
  //
  // PASS [mem:    805, cpu:    1663641] cheap hash
  // crypto.sha3_256(#"") == #"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"
  //
  // PASS [mem:    805, cpu:     434990] cheap hash
  // crypto.sha2_256(#"") == #"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  //
  // PASS [mem:    805, cpu:     357676] cheaper hash
  // crypto.blake2b_224(#"") == #"836cc68931c2e4e3e838602eca1902591d216837bafddfe6f0c8cb07"
  //
  // PASS [mem:    805, cpu:     351411] cheapest hash
  crypto.blake2b_256(#"") == #"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8"
}

blake2b_256 is the cheapest hash on chain btw.

If we are going force users to use blake2b_224 then we really need to update this line of text:

https://github.com/aiken-lang/stdlib/blob/main/lib/aiken/crypto.ak#L63

because it makes it seem that we can not use it.

@perturbing
Copy link
Contributor

perturbing commented Sep 19, 2024

Hi,

Just want to reiterate that I am saying two different things. The first one is aimed at helping you solve a technical design issue, the second one is about this PR.

  1. You can use blake2b224 to map to the field, the current interface does not force you to do anything. And cool that they changed the costing, initially they were the same cost. But also note that your above costing is with an input size of 0, which means the linear term in the costing is not considered (the slope of blake2b224 is a bit lower). So, it depends on which one is cheaper ;p Also note that if you use blake2b224, you save the CEK cost and the built-in cost of calling the mod reduction / byte string slice, so the full picture is a bit more nuanced.

  2. It's generally considered bad if an interface breaks assumptions. The type interface for Scalar should fail if an incorrect number is given. This does not mean that you as a dApp developer cannot modulo reduce a value in a certain setting. But in the general case, it is best to not break this assumption, as this can lead to unintended security breaches of other crypto protocols.

Given point 2, I am against the change of adding a mod reduction to the Scalar interface to make it never fail (which this PR is about). Note that I am not a maintainer of this repo, so this is just my 2 cents.

@logicalmechanism
Copy link
Contributor Author

  1. It's generally considered bad if an interface breaks assumptions. The type interface for Scalar should fail if an incorrect number is given. This does not mean that you as a dApp developer cannot modulo reduce a value in a certain setting. But in the general case, it is best to not break this assumption, as this can lead to unintended security breaches of other crypto protocols.

While interfaces should ideally enforce certain constraints to prevent misuse, in the context of finite fields, allowing scalar values larger than the field prime isn't problematic because the underlying arithmetic ensures correctness through modulo reduction.

In this example below, we select a number larger than the field prime. It works because it is the same as taking that number modulo the field prime then doing the operation.

test generator_2() {
  builtin.bls12_381_g1_scalar_mul(
    64942298445558615972166883114756892612800147825978349404662577504761794852281,
    generator,
  ) == builtin.bls12_381_g1_scalar_mul(
    12506423270432425492719142606570926775109595325450711582058918804823213667768,
    generator,
  )
}

Assume we modulo the field prime:

test new_2() {
  new(
    64942298445558615972166883114756892612800147825978349404662577504761794852281,
  ) == Some(
    Scalar(
      12506423270432425492719142606570926775109595325450711582058918804823213667768,
    ),
  )
}

So its not going to have unintended security breaches, that is just not how the arithmetic over finite fields works. It does not break anything.

@perturbing
Copy link
Contributor

It works because it is the same as taking that number modulo the field prime then doing the operation.

test generator_2() {
 builtin.bls12_381_g1_scalar_mul(
   64942298445558615972166883114756892612800147825978349404662577504761794852281,
   generator,
 ) == builtin.bls12_381_g1_scalar_mul(
   12506423270432425492719142606570926775109595325450711582058918804823213667768,
   generator,
 )
}

It works because the built-in function must be well-defined over their domain, and there is no dedicated scalar type in plutus. Meaning that the code behind that built-in does it for you. This for safety of the underlying scalar type in blst, otherwise the C FFI calls fail.

test new_2() {
  new(
    64942298445558615972166883114756892612800147825978349404662577504761794852281,
  ) == Some(
    Scalar(
      12506423270432425492719142606570926775109595325450711582058918804823213667768,
    ),
  )
}

This ambiguity is precisely what one would like to prevent. Constraining the user provided scalars is good, as it can prevent in its most basic form replay attacks. For example, say you have some proof of a statement which includes a scalar. If I add the field prime to one of those scalars (as an integer), the proof looks different, but your method of accepting that proof by modulo reducing it to the scalar, would still accept the proof.

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