The HashSquid project features 256 generative art hashSquid NFTs which are able to be stolen using a built-in contract function.
This is a unique twist on the gamification of NFTs. The objective in this game is to obtain and maintain ownership of hashSquid NFTs.
Each hashSquid NFT has two attributes: a baseHash
(bytes32) and a guardSum
(uint16). To steal an NFT, an input
(bytes32) must be found where hashSum(keccak256(baseHash, input))
is below the NFT's current guardSum
. So, the guardSum
protects the NFT from being stolen: the lower it is, the more difficult it is to steal. (See helper scripts to find good hashes in Python and C)
hashSquid NFTs with low guardSums
are worth more, since they are harder to steal.
-- 256 total NFTs
-- Minting fee is 2 FTM
-- Fees accumulate during the minting period
-- When the final NFT is minted, the owner of the NFT with the lowest guardSum is paid 90% of the contract balance (accrued from fees)
-- After minting period, reflip fees are withdrawable by contract owner
-- Stealing (function: reflip) fee is 5 FTM
-- An NFT can be stolen (fee: 5 FTM) if a bytes 32 `input` is found where hashSum(keccak256(baseHash, input)) < guardSum
-- An NFT's guardSum can be updated (owner only) if an input hash is found that satisfies the hashSum condition above
-- When an NFT 1) is stolen or 2) has its guardSum updated, the guardSum is updated with the new, lower value
-- If an NFT is stolen while it is listed for sale on an NFT marketplace, the marketplace's approval for the NFT is removed and the sale becomes invalid
When an NFT is transferred, the baseHash changes.
FTM CA:
Consider the keccak256 hash of 1234
(string):
0x387a8233c96e1fc0ad5e284353276177af2186e7afa85296f106336e376669f7
.
This hash can be expressed as an array of HexBytes (with possible values 0-255):
[56,122,130,51,201,110,31,192,173,94,40,67,83,39,97,119,175,33,134,231,175,168,82,150,241,6,51,110,55,102,105,247]
the hashSum
of this byte array is just the sum of the decimals at each position, so 3670
in this case. The histogram below shows the hashSum
distribution for 100,000 keccak256
hashes:
The average keccak256 hashSum
after 100,000 iterations is 4080 = (8160/2). The distribution is normally distributed around the average; finding hashSums
further away from 4080 in either +/- direction becomes increasingly difficult. One out of 2^256 inputs
is expected to give a keccak256
hash with all zeros, i.e. with a hashSum
of zero. Since the maximum possible value in the byte array is 255, and there are 32 positions in the array, the maximum possible keccak256 hashSum
is 8160.
Each hashSquid NFT maps to a baseHash
, and to steal an NFT, an input
(bytes32) must be found which hashes together with the NFT's baseHash
to produce a hashSum
less than the NFT's current guardSum
. When an NFT is transferred, the baseHash changes.
Let's say an NFT's baseHash
is 0x387a8233c96e1fc0ad5e284353276177af2186e7afa85296f106336e376669f7
and the NFT's current guardSum
is 2150
. First, let's test input
(bytes32) of 0x47a4ac6742dabb8c531c6e2b6fba383e56619dec9bdb4937dd50a707bf24bd02
.
keccak256(0x387a8233c96e1fc0ad5e284353276177af2186e7afa85296f106336e376669f7, 0x47a4ac6742dabb8c531c6e2b6fba383e56619dec9bdb4937dd50a707bf24bd02), =
0x341b03593d0724275b8a4be9cb2931c2175a629a320e0c14011981fa3668404a =
[52,27,3,89,61,7,36,39,91,138,75,233,203,41,49,194,23,90,98,154,50,14,12,20,1,25,129,250,54,104,64,74] =
hashSum 2500
2500 < 2150
is false
, so this input
doesn't work. Now let's try input
of 0x464644dbcbdae1877b785211cd97613701b04b286387a3a673f5cff6952f777b
.
keccak256(0x387a8233c96e1fc0ad5e284353276177af2186e7afa85296f106336e376669f7, 0x464644dbcbdae1877b785211cd97613701b04b286387a3a673f5cff6952f777b) =
0x276327ee126b2509c37209ca127d2a3e4f03280657541e0d865d4521020d1751 =
[39,99,39,238,18,107,37,9,195,114,9,202,18,125,42,62,79,3,40,6,87,84,30,13,134,93,69,33,2,13,23,81] =
hashSum 2143
2143 < 2150
is true
, so this input
works. Now the NFT can be stolen. We just call reflip(input (bytes32), tokenID)
on the contract, and the NFT will be transferred to msg.sender
.
For now, the easiest way to call these functions is on the ftmscan blockchain explorer.
Mint
No input accepted. Max 256 mintings.
Minted NFT's baseHash set to `keccak256(block.timestamp, msg.sender)
Minted NFT's guardSum set to `keccak256(msg.sender, tokenId)
updateGuardSum (bytes32 data, uint256 tokenId)
Only the owner of the NFT with tokenId can call this function
Checks if hashSum(keccak256(baseHash, data)) < guardSum
If it is, update the NFT's guardSum
reflip (bytes32 data, uint256 tokenId)
i.e. pilfer, get it?
Only non-owners can call this function
Checks if hashSum(keccak256(baseHash, data)) < guardSum
If it is, update guardSum
and transfer ownership to msg.sender
getBaseHash ()
Given an NFT tokenId, return the current baseHash
getGuardSum ()
Given an NFT tokenId, return the current guardSum
python find_hash.py <guardSum> <baseHash (with 0x)>
git clone https://github.com/brainhub/SHA3IUF
cd SHA3IUF
cp ../find_hash.c ./
gcc -o find_hash find_hash.c sha3.c
./find_hash <guardSum> <baseHash (without 0x)>
update find_hash.c to work on GPU