Visitar URL original
Keccak streaming API by msprotz · Pull Request #803 · hacl-star/hacl-star · GitHub
Skip to content

Keccak streaming API - #803

Merged
msprotz merged 41 commits into
mainfrom
protz_keccak_streaming
May 3, 2023
Merged

msprotz merged 41 commits into
mainfrom
protz_keccak_streaming

Conversation

@msprotz

@msprotz msprotz commented Apr 14, 2023 •

Copy link
Copy Markdown
Contributor

Hot on the heels of #800, this PR adds a new instance of the streaming functor specifically for algorithms of the Keccak family.

Background

The streaming functor currently comes in several flavors of agility (the ability to parameterize the code over a choice of algorithm or implementation).

  • Static agility: the instance is generic, but must be specialized at compile-time before extraction. Examples: Poly1305 (generic of the choice of implementation: 32, 128 and 256), Blake2 (generic over both algorithm and implementation: Blake2b/32, Blake2b/256, Blake2s/32, Blake2s/128), and MD (generic over the choice of algorithm: MD5, SHA1, SHA2). Concretely, you have:
inline_for_extraction noextract
let hacl_md (a:alg)// : block unit =
  =
  Block

and you do:

let update_512 = F.update (hacl_md SHA2_512) (G.hide ()) ((state_t SHA2_512).s ()) (G.erased unit)

Things are done this way because SHA2_512 and SHA2_256 have different state representations, so you can't extract to Low* a type of the form lbuffer (if SHA2_256 then uint32 else if SHA2_512 uint64) -- you have to pick either one at compile time and rely on F* to reduce it to either lbuffer uint32 or lbuffer uint64 before sending it over to krml.

  • Runtime agility: the state is an inductive, indexed by the agility parameter type state a = | SHA2_256: lbuffer uint32 ... -> state SHA2_256 | SHA2_512: lbuffer uint64 ... -> state SHA2_512. This style compiles to C (as a tagged union), but creates more representation overhead and an extra level of indirection because now you have a reference to a state a that contains an lbuffer. No need to do compile-time specialization: the user picks at run-time which algorithm they want to run with.

This PR

This PR introduces a new style that is somewhat in-between the two, relying on the observation that all algorithms from the Keccak family share the same state representation.

For this streaming functor, the state becomes sha3_alg & lbuffer uint64 25, i.e. no need to have an inductive with different types under each branch.

Making it work required quite some delicate surgery, notably:

  • using a singleton type for the left component of the pair to tie it to the index, in order to justify that operations like copy really do operate on the same algorithm
  • judicious use of erasure, to enforce statically the fact that the instance of the streaming functor never requires the user to provide the algorithm at run-time (since it's already stored internally)
  • a few plays on types in Hacl.Hash.SHA3 to explain to F* that a Low*-compatible, monomorphic type that talks about lbuffer uint64 25 is really a refinement of the generic type expected by callers
  • a few touch-ups to make sure quality code is generated for the various functions
  • add a notion of variable output length to the streaming functor (for shake)
  • and many more little things.

I'm probably going to push a few cleanups but wanted to get this out to start the work of getting it green and gathering feedback. The F* code can be reviewed. The C code needs (obviously) to be fixed up because there are large diffs that shouldn't be there. The C code is ready, too.

This is the second half of the work required for adding SHA3 to python/cpython#99108

msprotz added 6 commits April 12, 2023 13:54
If the key is kept all the way until the end, the update_multi and
update_last functions may now receive the key, too.
Using a singleton type, I actually managed to pair up the algorithm with
a non-agile state type (that extracts to C). Using a few tricks related
to erasure (and marking a few more functions erased), I was able to
capture statically the fact that many Keccak streaming functions don't
need the client to pass the algorithm, as the information is either
stored internally, or irrelevant.
@msprotz
msprotz requested a review from a team as a code owner April 14, 2023 17:21
a, B.malloc r (Lib.IntTypes.u64 0) 25ul)
(* free: *) (fun _ (_, s) ->
B.free s)
(* copy: *) (fun _ (a, s_src) (a', s_dst) ->

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is where the singleton type is needed: because the two types share the same index (as imposed by the type signature of copy), we can conclude that the two values are exactly the same, meaning that a and a' are identical

Base automatically changed from protz_sha3_agile to main April 14, 2023 18:58

@R1kM R1kM left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The F* changes look reasonable so far. I'll have a look at the generated C code once you're done with the clean-up.

Comment thread code/hash/Hacl.Hash.SHA3.fsti Outdated
Comment thread code/streaming/Hacl.Streaming.Keccak.fst
@github-actions

github-actions Bot commented Apr 14, 2023 •

Copy link
Copy Markdown

[CI] Important!
The code in dist/gcc-compatible, dist/msvc-compatible, and dist/wasm
is tested on cryspen/hacl-packages.
dist is not automatically re-generated, be sure to do it manually.
(A fresh snapshot can also be downloaded from CI.)
Then check the following tests before merging this PR.
Always check the latest run, it corresponds to the most recent version of this branch.
All jobs are expected to be successful.
In some cases manual intervention is needed. Please ping the hacl-packages maintainers.

  • Build, Test, Benchmark: Build on Linux (x86, x64), Windows (x86, x64), MacOS (x64, arm64), s390x, Android (arm64) and test on Linux (x86, x64), Windows (x86, x64), MacOS (x64).
  • Performance Regression Tests: Navigate to the terminal output in “Run benchmarks”. The comparison with the main branch will be at the bottom. The run fails if the performance regresses too much.
  • OCaml bindings: Build & Tests
  • JS bindings: Build & Tests
  • Rust bindings: Build & Tests

@msprotz

msprotz commented Apr 15, 2023

Copy link
Copy Markdown
Contributor Author

@R1kM I think the C code is where I want it to be. I'm still wrestling with CI.

Comment thread dist/gcc-compatible/Hacl_Hash_SHA3.c Outdated
uint8_t *block = blocks + i * block_len(a);
Hacl_Impl_SHA3_absorb_inner(block_len(a), block, s);
}
uint8_t *last = blocks + n_blocks * block_len(a);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need this last statement?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

https://github.com/hacl-star/hacl-star/blob/main/lib/Lib.Buffer.fst#L351

Not sure how to get rid of it. Maybe another peephole optimization in krml?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the reason unused variable elimination doesn't kick in is because we don't know that block_len is pure, and therefore can be eliminated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Aymeric and I talked about this, and the conclusion is that we'd need repeat_blocks_multi (with a suitable precondition that length input % block_length == 0). I believe we don't have this combinator yet, but @polubelova can confirm.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

that's correct, we don't have loop_blocks_multi in Lib.Buffer.fst

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok no problem let me add it it's not a big deal

@msprotz

msprotz commented Apr 17, 2023

Copy link
Copy Markdown
Contributor Author

I addressed all of the low-hanging fruits. The rest is somewhat longer-ish discussions, so please feel free to file separate issues so that we don't lose track of your remarks. Thanks for the review!

@msprotz

msprotz commented Apr 18, 2023

Copy link
Copy Markdown
Contributor Author

@franziskuskiefer bringing your attention to this as it changes the SHA3 streaming API to a more general one for all Keccak constructions -- you now need to pass your choice of algorithm at initialization-time, and the finish function now has two modes, depending on whether the initial choice of algorithm is shake (variable length output) or not (fixed length output solely dependent on the variant of sha3 picked)$:

  • finish(st, dst, 0) when not (is_shake st) -- the user MUST pass 0 to indicate that they are aware that this is a non-variable-length algorithm
  • finish(st, dst, l) whwen is_shake st -- the user MUST pass a non-zero length l

The API is defensive and will return a non-zero error code to indicate failure if the user fails to obey the MUST clauses.

The alternative design is to have two functions, one called finish, and another one called finish_shake, where only the latter takes a length argument. I don't have strong feelings either way, so opinions welcome.

@franziskuskiefer

Copy link
Copy Markdown
Member

@franziskuskiefer bringing your attention to this as it changes the SHA3 streaming API to a more general one for all Keccak constructions

Looks like didn't actually respond here 😬 Here my thoughts.

  1. I think the implicit switch in the finish through the argument is not great.
  2. Why do you need an argument in the finish function? When you made your choice in the init, this shouldn't be necessary, i.e. you can put in the length there too.
  3. Using 0/1 arguments are always bad, because the caller doesn't know what they stand for. The better approach would be to define a SHAKE and SHA (or whatever else), that should be used.
  4. Looking at the previous points, adding a separate function might be the easiest though.

@msprotz

msprotz commented May 3, 2023

Copy link
Copy Markdown
Contributor Author

I've implemented the requested changes. Can either one of @franziskuskiefer or @karthikbhargavan mark this as approved? I'd like to land this soon, it's been a while

@karthikbhargavan karthikbhargavan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, much needed changes.

@franziskuskiefer franziskuskiefer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks :shipit:

@msprotz
msprotz enabled auto-merge May 3, 2023 17:45
@msprotz
msprotz merged commit ab3499d into main May 3, 2023
@msprotz
msprotz deleted the protz_keccak_streaming branch May 3, 2023 23:30
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.

5 participants