With our RISC-V scrypt-hash program compiled, we must now pack it for usage within a Cartesi Machine. To do that, we will build an
ext2 file-system with the necessary contents, similar to what was done for the GPG Verify tutorial.
To do that, first copy the
scrypt-hash executable and the
libscrypt shared library we compiled before to a directory called
mkdir ext2cp scrypt-hash ext2cp libscrypt/libscrypt.so.0 ext2
Then, still inside the playground, use the
genext2fs tool to generate the file-system with those contents:
genext2fs -b 1024 -d ext2 scrypt-hash.ext2
As such, the generated
scrypt-hash.ext2 file now represents an
ext2 file-system containing our scrypt hashing program.
At this point, to finally compute hashes using a Cartesi Machine, all we need is some data. We can start by grabbing the header for Dogecoin block #100000, whose relevant field values in hexadecimal notation are the following:
0x52fd869d(corresponds to datetime
2014-02-13 18:59:41 -0800, or
1392346781in decimal notation)
2216773632in decimal notation)
As explained in the technical background, the hashing algorithm's input data can be derived by simply concatenating all those hexadecimal values. The resulting 80 bytes long hexadecimal string can then be written as a binary file with the following command, using the
echo "0000000212aca0938fe1fb786c9e0e4375900e8333123de75e240abd3337d1b411d14ebe31757c266102d1bee62ef2ff8438663107d64bdd5d9d9173421ec25fb2a814de52fd869d1b267eeb84214800" | xxd -r -p > input-doge100000.raw
We can also generate an adulterated invalid block header input, just to see how our hashing service behaves. Here, we will simply change the
Nonce value from
0x84214801, which corresponds to changing the last digit of the concatenated hex string, as follows:
echo "0000000212aca0938fe1fb786c9e0e4375900e8333123de75e240abd3337d1b411d14ebe31757c266102d1bee62ef2ff8438663107d64bdd5d9d9173421ec25fb2a814de52fd869d1b267eeb84214801" | xxd -r -p > input-doge100000-invalid.raw
Finally, as discussed in other tutorials and in the Cartesi Machine host perspective section, we need to use the
truncate tool to pad all drive files to 4K, which is the minimum required length for usage with Cartesi Machines:
truncate -s 4K input-doge100000.rawtruncate -s 4K input-doge100000-invalid.rawtruncate -s 4K output.raw
Now that we have all of the necessary resources in place, let's perform some hash computations!
Still within the playground, execute the following command to run the hashing algorithm for the
cartesi-machine \--flash-drive="label:scrypt-hash,filename:scrypt-hash.ext2" \--flash-drive="label:input,length:1<<12,filename:input-doge100000.raw" \--flash-drive="label:output,length:1<<12,filename:output.raw,shared" \-- $'cd /mnt/scrypt-hash ; ./scrypt-hash $(flashdrive input) $(flashdrive output)'
This should yield the following output, showing that our code is being successfully executed within the Cartesi Machine:
./ \/ \\---/---\ /----\\ X \\----/ \---/---\\ / CARTESI\ / MACHINE'Reading input data...Computing scrypt hash...Writing computed scrypt hash to output...DONE!HaltedCycles: 94719700
After the execution, we can use the
xxd tool again to verify the result written to the first 32 bytes of the output drive:
xxd -p -l 32 -c 32 output.raw00000000002647462b1abb10059b1f6f363acbc93f581cc256cc208e0895e5c7
Notice the leading zeros, which indicate a relatively small number. Recalling the explanation of the
Bits field given in the technical background, the target hash value for a valid block header with the given
Bits value of
0x1b267eeb is given by:
target = 267eeb << 8*(1b - 3) =0000000000267eeb000000000000000000000000000000000000000000000000
Comparing this value to the computed hash above, we can observe that our result is indeed slightly smaller than the required target. This confirms that the given block header is indeed valid! Wow, such computation!
To make sure that our hashing algorithm implementation is really working, let's also run the machine for the adulterated version of the input data:
cartesi-machine \--flash-drive="label:scrypt-hash,filename:scrypt-hash.ext2" \--flash-drive="label:input,length:1<<12,filename:input-doge100000-invalid.raw" \--flash-drive="label:output,length:1<<12,filename:output.raw,shared" \-- $'cd /mnt/scrypt-hash ; ./scrypt-hash $(flashdrive input) $(flashdrive output)'
This time, checking the resulting hash leads to the following output:
xxd -p -l 32 -c 32 output.raw3fc9f917be74f50bafc9bad28bf9ccda3e0c46b4af2e5bc78029926460f9100a
Which corresponds to a very high number, as should be expected when hashing random input data. This value is of course way higher than the required target, thus indicating that the given block header is invalid. We can now be sure to have a working Dogecoin block header validator running inside a Cartesi Machine!
Finally, now that we have completed our tests we can exit the playground by typing:
Following the same strategy used for the other tutorials, we will finish off our Cartesi Machine implementation by producing a bash script that allows us to easily build and appropriately store the machine's template specification. This way, the machine will be available for Descartes to instantiate computations whenever a smart contract requests it.
It should also be noted that, as discussed in the GPG Verify tutorial, the process of creating
ext2 file-systems using the
genext2fs tool is not reproducible. This means that each generated
ext2 file leads to a different Cartesi Machine template hash, even if the file-system's contents are identical. For this reason, to exactly reproduce this tutorial's results, you can download the actual scrypt-hash.ext2 file used when writing this documentation. To do that, run the following command:
rm scrypt-hash.ext2wget https://github.com/cartesi/descartes-tutorials/raw/master/dogecoin-hash/cartesi-machine/scrypt-hash.ext2
After that, let's create the
build-cartesi-machine.sh file inside the
touch build-cartesi-machine.shchmod +x build-cartesi-machine.sh
Then, edit the file and insert the following contents:
#!/bin/bash# general definitionsMACHINES_DIR=.MACHINE_TEMP_DIR=__temp_machineCARTESI_PLAYGROUND_DOCKER=cartesi/playground:0.3.0# set machines directory to specified path if providedif [ $1 ]; thenMACHINES_DIR=$1fi# removes machine temp store directory if it existsif [ -d "$MACHINE_TEMP_DIR" ]; thenrm -r $MACHINE_TEMP_DIRfi# builds machine (running with 0 cycles)# - initial (template) hash is printed on screen# - machine is stored in temporary directorydocker run \-e USER=$(id -u -n) \-e GROUP=$(id -g -n) \-e UID=$(id -u) \-e GID=$(id -g) \-v `pwd`:/home/$(id -u -n) \-w /home/$(id -u -n) \--rm $CARTESI_PLAYGROUND_DOCKER cartesi-machine \--max-mcycle=0 \--initial-hash \--store="$MACHINE_TEMP_DIR" \--flash-drive="label:scrypt-hash,filename:scrypt-hash.ext2" \--flash-drive="label:input,length:1<<12" \--flash-drive="label:output,length:1<<12" \-- $'cd /mnt/scrypt-hash ; ./scrypt-hash $(flashdrive input) $(flashdrive output)'# defines target directory as being within $MACHINES_DIR and named after the stored machine's hashMACHINE_TARGET_DIR=$MACHINES_DIR/$(docker run \-e USER=$(id -u -n) \-e GROUP=$(id -g -n) \-e UID=$(id -u) \-e GID=$(id -g) \-v `pwd`:/home/$(id -u -n) \-h playground \-w /home/$(id -u -n) \--rm $CARTESI_PLAYGROUND_DOCKER cartesi-machine-stored-hash $MACHINE_TEMP_DIR/)# moves stored machine to the target directoryif [ -d "$MACHINE_TARGET_DIR" ]; thenrm -r $MACHINE_TARGET_DIRfimv $MACHINE_TEMP_DIR $MACHINE_TARGET_DIR
With this script ready, the final Cartesi Machine template can finally be built and stored in the appropriate location within the Descartes SDK environment by executing the following command:
Running the above command should give you the following output, which includes the appropriate
templateHash value to use when instantiating this computation from a smart contract:
0: eca67c481cc8c07dc4dca6334c4eea75adb3868fd4b6da93711fb24d36803fbfCycles: 0Storing machine: please wait
Finally, we can
cd back to the
dogecoin-hash home directory: