-
Notifications
You must be signed in to change notification settings - Fork 28
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
new hash_to_field code / expand_message_md revisited #214
Conversation
2f6ef8f
to
fe40c2d
Compare
Is pairing-plus or whatever up to date? |
Not yet, that's next on my list. |
New test vectors would be very helpful |
Using this new method for obtaining field elements, does it still preserves the guarantees required by the random oracle construction? |
Yes. There are two ways to argue this. One is to recall that the underlying RO-to-curve construction is really defined in terms of independent elements of F. Using two hash functions H0 and H1 that are modeled as independent random oracles satisfies this requirement, but it's not the only way to satisfy it---hash_to_field (with The other slightly less direct way is to observe that the output of expand_message inside of hash-to-field gives (These are equivalent views of the world.) |
Let's assume |
I see what you're saying. The scenario you describe doesn't quite work because I went back and looked at CDMP08, and now I see where the issue is coming from. Section 5 talks about building longer outputs by computing H(X || 1), H(X || 2), etc. The important thing here is that there's no chaining of output blocks in that construction. And as you say, using chaining on values that the attacker knows makes it possible to distinguish. (In contrast, HKDF does use chaining, but always combines the chained value with a secret value that the attacker never learns, namely, PRK.) I think the fix is straightforward (don't chain, just hash b0 over and over), but I'm going to think carefully about it and push some new text / code. Thank you!!! |
This is exactly the scenario I had in mind, but I simplified to b0 and b1.
Exactly, another approach is to generate |
OK, consider the following:
This avoids the chaining issue, because the attacker never sees If we were extremely paranoid, we might not even output So I guess I'm slightly in favor of outputting b_0_chopped, but I can be convinced otherwise. Thoughts @armfazh? One issue with the above that makes me quite nervous is that now only 1 byte (really, in the worst case, only 1 bit!!!) of the input of H is changing for each b_i. This is quite aggressive. A slightly better solution would be to do something like
The big downside of this is that it significantly increases the number of hash invocations. We could get around that with something like:
where |
A third approach is to chain, but chop the output of every hash function, not just the first one. (The last block does not need to be chopped in this case.) Let's think about the cost of all three approaches. For all three approaches, we have
For subsequent blocks,
Type 6 doesn't really make any sense, but I've included it for completeness. Let's look first at output rates for different cases:
Types 4, 5, and 6 correspond to types 1, 2, and 3 (respectively), except that the "first block" column is always 0 (because Now, let's look at number of compression function invocations. Assume that the input message is 32 bytes and DST is 16 bytes; this is roughly worst case (because long messages will dominate hashing cost).
Closed-form expressions for ell and number of invocations. Below,
Using the above, let's consider the cost of different scenarios:
Not surprisingly, type 2 is the cheapest across the board. Maybe slightly more surprisingly, types 1 and 3 have close to the same cost, but type 3 is basically always better.
EDIT I think the "right" choice is really type 5, even though it costs one extra hash invocation for very short messages and very short outputs. So that's my vote. Type 2 costs fewer hashes, but I'm less certain about its security. I'm roughly as confident in type 4 as type 5, but it's much more expensive. I'm least confident about type 3. And as I said above, type 6 really doesn't make any sense... |
Just to be clear, I'll write down the pseudocode for each one: All of them start in roughly the same way:
Type 1
Type 2
Here, Type 3
|
Actually, it occurs to me that the construction as currently written (i.e., where The reason is, the field elements at the output of hash-to-field do not uniquely determine the bytes at the output of expand-message. In all cases, we take (log(q) + k) bits and reduce them to log(q) bits. Hand-wavily, this means that the indifferentiability simulator has about 2^k choices it can make when mapping field elements to byte strings. The proof is much cleaner if we can just make expand-message a random string, so probably it's best to do that. For this purpose I favor approach 2, since it adds the least overhead... |
OK, I revamped my comment above to include options that don't output any part of My vote is to go with "type 5." This is maybe slightly conservative and will make embedded folks slightly sadder. I'll update the document and code with proposed changes (we can always back them out). |
Type 5 seems like the right compromise on balance. We maintain chaining while also mixing in Thanks @armfazh for pointing out the issue! |
the new function is 'type 5' as discussed in cfrg#214.
the new function is 'type 5' as discussed in cfrg#214.
I talked with Dan (Boneh) about "type 5" today, and he had two suggestions:
It occurs to me that an alternative solution to both of the above issues is to switch the order of the inputs to the hash function; this guarantees that the critical inputs are always right at the beginning. In particular,
This means we're doing domain separation as a suffix rather than as a prefix of the message. Does this make life more difficult when interacting with other protocols? I'm not exactly sure, but I can't see why it should. In fact, it arguably makes life better, because any protocol that needs to compute another value of Note that both of the worries above apply to SHA-2; for SHA-3, we don't really need to worry about any of this. |
addresses a suggestion from Dan regarding breaking b_0 or msg across blocks
Quick note: I pinged Dan about swapping the input order and he said it looked fine. |
Arrgh. As I was writing down a proof sketch I found an issue with swapping the argument order. Fortunately, it's not hard to fix, and there's a reasonably easy way of avoiding any performance ramifications. Next, I'm going to push a commit that implements this. As always, we can revert... Executive summaryAbove I proposed computing
To make the proof go through, we should change this to
In other words, we should prepend 1 (input) block of 0s to msg before hashing. For SHA-256 this is 64 bytes, for SHA-512 it's 128 bytes. This lets us rely directly on the security proofs for NMAC given in CDMP05, as well as other analysis like Bel06. In the rest of this post I'll first discuss why we ought to do this, and then describe how implementors can avoid actually computing another hash function invocation. Why do we need this change?Previously we were arguing that our construction was an implementation of the NMAC construction (really, the confusingly-named HMAC^f construction; for clarity I'll just call it NMAC) in Section 3.5 of CDMP05. The basis for this claim was that the first block of each invocation of H was unique, since we had the DST and the counter in there. (We can understand Dan's suggestion (1) above, padding the first block of the b_0 computation, as strengthening this argument.) Unfortunately, if we're putting the message first, we can't directly rely on the NMAC proof: since the adversary controls the first input block to the hash function, we have no guarantee that it's unique. CDMP05 fix this problem by prepending a block of 0s to the message. This works because it prevents the adversary from controlling the contents of the first block. Using our notation, this is secure essentially because the probability is negligible that b_0 == 0 or strxor(b_0, b_(i-1)) == 0. What is the cost?Directly implementing this change costs an extra compression function invocation. But in practice it's possible to avoid this, just by saving the state of the hash function after it ingests 1 block of 0s, and hashing msg starting from that state. The storage cost to implement this optimization is minimal (less than 100 bytes for the SHA-2 family), and in practice lots of SHA-2 implementations already let you do this. (This is true, for example, of the OpenSSL implementation.) The other cost here is that it puts us back in a universe where we can't share a hash prefix with other computations of |
expand_message_xmd now instantiates NMAC in a secure way
@@ -1651,7 +1651,7 @@ In this case, security holds when the inner function is modeled as a | |||
random transformation or as a random permutation {{BDPV08}}. | |||
|
|||
- Otherwise, H MUST be a hash function that has been proved indifferentiable | |||
from a random oracle {{MRH04}} under a reasonable cryptographic assumption. | |||
from a random oracle {{MRH04}} under a widely accepted cryptographic assumption. |
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.
:)
Referencing the proof wholesale is great! Most hash function implementations allow the internal state to be set, so I don't foresee this to be a problem in practice (as you mention). |
hyphen -> underscore also, 'SHA.256' -> 'SHA-256' and similar closes cfrg#213
@armfazh are you OK with this change? If so, I’ll merge it. |
I am not sure whether the XOR maintains the security, or gives the attacker a new surface for attacks. I am not able to prove or disprove this claim. |
This PR updates the code in
poc/
to use the new hash_to_field function from #212.I have not yet regenerated the test vectors. Shall I do that?