I finally got some time to work on the instructions of the Secret exercise. Please find the revised instructions below. I hope that you find the instructions clear, though you also might find them boring. If the proposed revision look good to you guys, I would make similar chnages to the comments in secrets.asm along the same line and then create a PR.
Instructions
Your friend wants to secretly send you important messages, each of which is encoded in a 16-bit integer.
To keep anyone but you from reading his message, he does not send you the message itself.
Instead, he sends you a 16-bit integer that you can transform into the message if you follow the correct procedure.
These are the single bit instructions mentioned in this concept:
| Name | Description |
|------|-------------------------------------------------------------------------------|
|bt |copies the bit into `CF` without modifying any operand |
|bts |copies the bit into `CF` and sets it in the destination operand |
|btr |copies the bit into `CF` and clears it in the destination operand |
|btc |copies the bit into `CF` and complements (flips) it in the destination operand |
These are the bitwise instructions mentioned in this concept:
| Name | Description |
|--------|----------------------------------------------|
|and |1 if both bits are 1 |
|or |1 if at least one of the bits is 1 |
|xor |1 if the bits differ |
|not |1 if bit was 0; 0 if bit was 1 |
These are the shift instructions mentioned in this concept:
| Name | Description |
|---------|-------------------------|
|shl/sal |Shifts bits to the left |
|shr/sar |Shifts bits to the right |
These are the rotation instructions mentioned in this concept:
| Name | Description |
|---------|-----------------------------------------------|
|rol |Rotates bits to the left |
|ror |Rotates bits to the right |
|rcl |Rotates bits to the left, including the `CF` |
|rcr |Rotates bits to the right, including the `CF` |
These are the miscellaneous instructions mentioned in this concept:
| Name | Description |
|----------|----------------------------------------------------------------------------------------|
| popcnt | Counts the number of bits set |
| lzcnt | Counts the number of leading zeros (a bit cleared before the first set bit) |
| bsr | Gets the index of the most significant set bit. If no bit is set, result is undefined |
| bsf | Gets the index of the least significant set bit. If no bit is set, result is undefined |
1. Extract the mask
In processing a 16-bit integer you receive from your friend, you have to extract the upper and lower bytes from the integer X.
X = b15 b14 b13 b12 b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0
upper byte (U) lower byte (L)
Implement the extract_higher_bits function that takes a 16-bit integer and returns the upper byte (bits 15–8) of it.
extract_higher_bits(0b1010010011000101)
// => 0b10100100
2. Extract the message
Implement the extract_lower_bits function that takes a 16-bit integer and returns the lower byte (bits 7–0) of it.
extract_lower_bits(0b1010010011000101);
// => 0b11000101
3. Extract redundant bits
Given a 16-bit integer X, let U and L be the upper and lower bytes of it, respectively.
We call the i-th bit position redundant if the i-th bit is set in both U and L (i = (0..7)).
You will later need the number of redundant positions of the 16-bit integer from your friend.
Implement the extract_redundant_bits that takes a 16-bit integer and returns the 8-bit mask that selects the redundant bit positions.
extract_redundant_bits(0b1010010011000101);
// => 0b10000100
4. Set all message bits
Given a 16-bit integer X, let U and L be the upper and lower bytes of it, respectively.
Later, for some 16-bit integer X, you will need an 8-bit integer obtained by treating U as a mask and setting each bit of L selected by U (i.e., the resulting 8-bit integer is the bitwise OR of L and U).
Implement the set_message_bits function that takes a 16-bit integer and returns the 8-bit integer obtained by setting the bits of the lower byte selected by the upper byte (mask).
set_message_bits(0b1010010011000101);
// => 0b11100101
5. Rotate private key
To successfully transform a 16-bit integer into a message, you need the private key shared by you and him, which is 0b1011001100111100.
Suppose that X is the 16-bit integer you receive from your friend.
In applying the shared private key, you first need to rotate the bits of the key to the left by the number of set bits in extract_redundant_bits(X).
Implement the rotate_private_key function that takes a 16-bit integer X and returns the 16-bit integer obtained by rotating the shared private key.
rotate_private_key(0b1010010011000101);
// => 0b1100110011110010
NASM (The Netwide Assembler, the assembler used by this track) has support to constants in binary format with `0b` prefixed.
It also supports using an underscore (`_`) as separator in a constant, for readability:
```x86asm
PRIVATE_KEY equ 0b1011_0011_0011_1100
```
6. Format private key
To use the shared private key in the transformation of the 16-bit integer into the message, you have to “format” the key.
To format the shared private key, you must:
- Rotate it (see Step 5).
- Let
M and B denote the upper and lower bytes of the rotated key, respectively.
- Take the bitwise XOR of
M and B.
- Take the bitwise NOT of the resulting 8-bit integer.
Implement the format_private_key function that takes a 16-bit integer and returns the formatted 8-bit private key.
format_private_key(0b1010010011000101);
// => 0b11000001
7. Get the message
Given a 16-bit integer X you receive from your friend, the message your friend intends to send is the 16-bit integer whose upper and lower bytes are format_private_key(X) and set_message_bits(X), respectively.
Implement the decrypt_message function that takes a 16-bit integer and returns the message hidden in it.
decrypt_message(0b1010010011000101);
// => 0b1100000111100101