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

Hash arbitrary number of elements in masm #750

Merged
merged 11 commits into from
Jul 2, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- Created `get_serial_number` procedure to get the serial num of the currently processed note (#760).
- [BREAKING] Added support for input notes with delayed verification of inclusion proofs (#724, #732, #759, #770, #772).
- [BREAKING] Added support for conversion from `Nullifier` to `InputNoteCommitment`, commitment header return reference (#774).
- Added `compute_inputs_hash` procedure for hash computation of the arbitrary number of note inouts (#750).

## 0.3.0 (2024-05-14)

Expand Down
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/architecture/transactions/procedures.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ To import the note procedures, set `use.miden::note` at the beginning of the fil
| `get_assets` | `[dest_ptr]` | `[num_assets, dest_ptr]` | note | <ul> <li>Writes the assets of the currently executing note into memory starting at the specified address `dest_ptr `. </li><li> `num_assets` is the number of assets in the currently executing note.</li> </ul> |
| `get_inputs` | `[dest_ptr]` | `[dest_ptr]` | note | <ul> <li>Writes the inputs of the currently executed note into memory starting at the specified address, `dest_ptr`. </li> </ul> |
| `get_sender` | `[]` | `[sender]` | note | <ul> <li>Returns the `sender` of the note currently being processed. Panics if a note is not being processed. </li> </ul> |
| `compute_inputs_hash` | `[inputs_ptr, num_inputs]` | `[HASH]` | note | <ul> <li>Computes hash of note inputs starting at the specified memory address.</li> </ul> |


### Tx
Expand Down
169 changes: 168 additions & 1 deletion miden-lib/asm/miden/note.masm
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,171 @@ export.get_serial_number

syscall.get_note_serial_number
# => [SERIAL_NUMBER]
end
end

#! Computes hash of note inputs starting at the specified memory address.
#!
#! This procedure divides the hashing process into two parts: hashing pairs of words using
#! `absorb_double_words_from_memory` procedure and hashing the remaining values using the `hperm`
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
#! instruction.
#!
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
#! Inputs: [ptr, num_elements]
#! Outputs: [HASH]
#! Cycles:
#! - If number of elements divides by 8: 56 cycles + 3 * words
#! - Else: 189 cycles + 3 * words
#!
#! Panics if num_elements is equals 0 or greater than 128.
export.compute_inputs_hash
# check that number of inputs is less than 128
dup.1 push.128 u32assert2 u32lte assert

# move number of inputs to the top of the stack
swap
# => [num_elements, ptr]

# check that number of inputs greater than 0
dup eq.0 assertz
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
# => [num_elements, ptr]

# get the number of double words
u32divmod.8 swap
# => [num_elements/8, num_elements%8, ptr]

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this should say "hash_memory_even" procedure.

# get the end_addr for hash_memory procedure (end address for pairs of words)
mul.2 dup.2 add movup.2
# => [ptr, end_addr, num_elements%8]

bobbinth marked this conversation as resolved.
Show resolved Hide resolved
# get the padding flag to add it to the capacity part
dup.2 eq.0 not
# => [pad_flag, ptr, end_addr, num_elements%8]

# prepare hasher state for RPO permutation
push.0.0.0 padw padw
# => [C, B, A, ptr, end_addr, num_elements%8]

# hash every pair of words
exec.native::hash_memory_even
# => [C', B', A', ptr', end_addr, num_elements%8] where ptr' = end_addr

# hash remaining input values if there are any left
# if num_elements%8 is ZERO and there are no elements to hash
dup.14 eq.0
if.true
# clean the stack
exec.native::state_to_digest
swapw drop drop drop movdn.4
# => [B']
else
# load the remaining double word
mem_stream
# => [E, D, A', ptr'+2, end_addr, num_elements%8]

# clean the stack
movup.12 drop movup.12 drop
# => [E, D, A', num_elements%8]

# get the number of elements we need to drop
# notice that drop_counter could be any number from 1 to 7
push.8 movup.13 sub movdn.12
# => [E, D, A', drop_counter]

### 0th value ########################################################

# if current value is the last value to drop ("cycle" number equals to the number of values
# to drop), push 1 instead of 0 to the stack
dup.12 eq.1 swap

# we need to drop first value anyway, since number of values is not divisible by 8
drop movdn.6
# => [e_2, e_1, e_0, d_3, d_2, d_1, 0/1, d_0, A', drop_counter]

### 1st value ########################################################

# prepare the second element of the E Word for cdrop instruction
# if current value is the last value to drop ("cycle" number equals to the number of values
# to drop), push 1 instead of 0 to the stack
dup.12 eq.2 swap
# => [e_2, 0, e_1, e_0, d_3, d_2, d_1, 0/1, d_0, A', drop_counter]

# push latch variable onto the stack; this will be the control for the cdrop instruction
push.0
# => [latch = 0, e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter]

# get the flag whether the drop counter is equal 1
dup.14 eq.1
# => [drop_counter == 1, latch = 0, e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter]

# update the latch: if drop_counter == 1, latch will become 1
or
# => [latch', e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', drop_counter]

# save the latch value
dup movdn.14
# => [latch', e_2, 0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', latch', drop_counter]

# if latch == 1, drop 0; otherwise drop e_1
cdrop
# => [e_2_or_0, e_1, e_0, d_3, d_2, d_1, 0, d_0, A', latch', drop_counter]

# move the calculated value down the stack
movdn.6
# => [e_1, e_0, d_3, d_2, d_1, 0, e_2_or_0, d_0, A', latch', drop_counter]

### 2nd value ########################################################

# repeat the above process but now compare drop_counter to 2
dup.13 eq.3 swap
movup.13 dup.14 eq.2 or
dup movdn.14
cdrop movdn.6
# => [e_0, d_3, d_2, d_1, 0, e_2_or_0, e_1_or_0, d_0, A', latch', drop_counter]

### 3rd value ########################################################

# repeat the above process but now compare drop_counter to 3
dup.13 eq.4 swap
movup.13 dup.14 eq.3 or
dup movdn.14
cdrop movdn.6
# => [d_3, d_2, d_1, 0, e_2_or_0, e_1_or_0, e_0_or_0, d_0, A', latch', drop_counter]

### 4th value ########################################################

# repeat the above process but now compare drop_counter to 4
dup.13 eq.5 swap
movup.13 dup.14 eq.4 or
dup movdn.14
cdrop movdn.6
# => [d_2, d_1, 0, e_2_or_0, e_1_or_0, e_0_or_0, d_3_or_0, d_0, A', latch', drop_counter]

### 5th value ########################################################

# repeat the above process but now compare drop_counter to 5
dup.13 eq.6 swap
movup.13 dup.14 eq.5 or
dup movdn.14
cdrop movdn.6
# => [d_1, 0, e_2_or_0, e_1_or_0, e_0_or_0, d_3_or_0, d_2_or_0, d_0, A', latch', drop_counter]

### 6th value ########################################################

# repeat the above process but now compare drop_counter to 6
dup.13 eq.7 swap
movup.13 movup.14 eq.6 or
cdrop movdn.6
# => [0, e_2_or_0, e_1_or_0, e_0_or_0, d_3_or_0, d_2_or_0, d_1_or_0, d_0, A']
# or in other words
# => [C, B, A', ... ]
# notice that we don't need to check the d_0 value: entering the else branch means that
# we have number of elements not divisible by 8, so we will have at least one element to
# hash here (which turns out to be d_0)

hperm
# => [F, E, D]

exec.native::state_to_digest
# => [E]
end
end

Loading