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

Supporting variants of SHA2 #1957

Merged
merged 50 commits into from
Feb 13, 2025
Merged

Supporting variants of SHA2 #1957

merged 50 commits into from
Feb 13, 2025

Conversation

querolita
Copy link
Member

Closes #1941

});

for (let { preimage, hash } of testVectors()) {
let actual = Gadgets.SHA2.hash(384, Bytes.fromString(preimage));
Copy link
Member

Choose a reason for hiding this comment

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

Nit: not sure about the convention, but as a user, reviewing the PR and running the tests, I would like to see some logs here, as it seems it is taking a bit of time to run. I was looking at the stdout for some seconds and stop it as I didn't know what it was running.

(again: not blocking at all)

{ runs: RUNS }
)(nobleSha384, async (x) => {
const { proof } = await Sha2_384Program.sha384(x);
await Sha2_384Program.verify(proof);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: is there a convention to check it also verifies? I added on my side.

const is_verified = await Sha2_384Program.verify(proof);
expect(is_verified);

I don't know if there is another place where we actually test the fact it verifies.

Copy link
Member

Choose a reason for hiding this comment

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

Good point.. we are not doing that currently, anyway, but we probably should start doing that for completness altought I am not completely sure if Pickles or Kimchi checks if a proof is valid after generating it as a sanity check already

compression,
messageSchedule,
padding,
initialState<T extends UInt32 | UInt64>(length: Length): T[] {
Copy link
Member

Choose a reason for hiding this comment

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

Unrelated to the PR: is there a convention in o1js to order the methods? As an engineer not working often in TS, I was surprised to see the method initialState defined after a place it is used (hash above).

Copy link
Member

Choose a reason for hiding this comment

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

Not really a convention, TS doesn't care - I personally try to order methods by factors such as importance or level of abstraction

// padding the message $5.1.1 into blocks that are a multiple of 512
let messageBlocks = padding<Type>(length, data);

let H = SHA2.initialState<Type>(length);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: it seems we're doing the Uint32/Uint64 transformations every time we execute hash. A direct optimization would be to do it once and for all.

Not blocking.

'0'.repeat(k) + // append k zero bits
'0'.repeat(lengthChunk - lBinary.length) + // append 64|128-bit containing the length of the original message
lBinary
).match(/.{1,8}/g)!; // this should always be divisible by 8
Copy link
Member

Choose a reason for hiding this comment

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

Oh. Is it a common practice in TS? :o

Copy link
Member

Choose a reason for hiding this comment

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

Not common, but it's something I had to do in the other SHA implementation a while ago - cant remember why tho

// Helper functions

// Helper function to check if it is a short SHA2 (SHA2-224 or SHA2-256) or not
function isShort(length: Length): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I would inline this check as isShort does not have a clear semantics for the primitive.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I didn't like it either... At first it was is256 but then it was true also in 224. Any suggestion?

Copy link
Member

@dannywillems dannywillems left a comment

Choose a reason for hiding this comment

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

Mostly comments at the moment.
I am not approving (sorry for being picky) because I would love to see :

  • the origin of the test vectors
  • a regression test for the number of constraints

I also think we should therefore get rid of sha256.ts if the primitive is implemented twice.

I have not fully verified that the circuits are not under-constrained.

@querolita querolita force-pushed the feature/eddsa/sha512 branch from 900b89a to bf788f3 Compare February 10, 2025 15:24
@@ -973,6 +974,27 @@ const Gadgets = {
*/
SHA256: SHA256,
Copy link
Member

Choose a reason for hiding this comment

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

let's deprecate it!

*/
hash<T extends Length>(length: T, data: FlexibleBytes): Bytes {
// Infer the type T based on the value of `length` (conditional type)
type Type = T extends 224 | 256 ? UInt32 : UInt64;
Copy link
Member

Choose a reason for hiding this comment

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

damn, some serious typescript! :D

compression,
messageSchedule,
padding,
initialState<T extends UInt32 | UInt64>(length: Length): T[] {
Copy link
Member

Choose a reason for hiding this comment

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

Not really a convention, TS doesn't care - I personally try to order methods by factors such as importance or level of abstraction

'0'.repeat(k) + // append k zero bits
'0'.repeat(lengthChunk - lBinary.length) + // append 64|128-bit containing the length of the original message
lBinary
).match(/.{1,8}/g)!; // this should always be divisible by 8
Copy link
Member

Choose a reason for hiding this comment

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

Not common, but it's something I had to do in the other SHA implementation a while ago - cant remember why tho


// Subroutines for SHA2

function Ch<T extends UInt32 | UInt64>(length: Length, x: T, y: T, z: T): T {
Copy link
Member

Choose a reason for hiding this comment

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

This works well, but if you are interested you could take a look at type guards that might make things in certain cases a bit cleaner

https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards

{ runs: RUNS }
)(nobleSha384, async (x) => {
const { proof } = await Sha2_384Program.sha384(x);
await Sha2_384Program.verify(proof);
Copy link
Member

Choose a reason for hiding this comment

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

Good point.. we are not doing that currently, anyway, but we probably should start doing that for completness altought I am not completely sure if Pickles or Kimchi checks if a proof is valid after generating it as a sanity check already

@querolita querolita merged commit 61970ca into main Feb 13, 2025
31 of 32 checks passed
@querolita querolita deleted the feature/eddsa/sha512 branch February 13, 2025 15:19
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.

sha2-512
5 participants