Около месяца назад я разговорился с dadev о парсинге идентификаторов токенов из общей коллекции OpenSea. Помимо того, что он научил меня чему-то новому, он превратился в действительно ценный разговор о битах, байтах, базовых разговорах и кодировании!
Замечательная команда CyberKongz (twitter) спрятала этот драгоценный камень в своем смарт-контракте, и возник вопрос: Как нам это разобрать?!
#Solidity function isValidKong(uint256 _id) pure internal returns(bool) { // making sure the ID fits the opensea format: // first 20 bytes are the maker address // next 7 bytes are the nft ID // last 5 bytes the value associated to the ID, here will always be equal to 1 // There will only be 1000 kongz, we can fix boundaries and remove 5 ids that dont match kongz if (_id >> 96 != 0x000000000000000000000000a2548e7ad6cee01eeb19d49bedb359aea3d8ad1d) return false; if (_id & 0x000000000000000000000000000000000000000000000000000000ffffffffff != 1) return false; uint256 id = (_id & 0x0000000000000000000000000000000000000000ffffffffffffff0000000000) >> 40; if (id > 1005 || id == 262 || id == 197 || id == 75 || id == 34 || id == 18 || id == 0) return false; return true; }
Web3.js FTW (для Интернета)
Лично я большой поклонник BitShields от Corso (коллекция по общему контракту OpenSea), так что эта коллекция была естественной для тестирования. Я выбрал 3 случайных элемента, поэтому посмотрите, смогу ли я разделить их с помощью утилит Web3.js:
https://opensea.io/assets/0x495f947276749ce646f68ac8c248420045cb7b5e/17773364155682651268275142113690502555604463656341524695465728811681819131905 https://opensea.io/assets/0x495f947276749ce646f68ac8c248420045cb7b5e/17773364155682651268275142113690502555604463656341524695465728854562772615169 https://opensea.io/assets/0x495f947276749ce646f68ac8c248420045cb7b5e/17773364155682651268275142113690502555604463656341524695465728795189144715265
И если свернуть это, мы можем увидеть некоторые общие элементы в «передней части» идентификатора токена, но, черт возьми, эта штука длинная! 😉
17773364155682651268275142113690502555604463656341524695465728811681819131905 17773364155682651268275142113690502555604463656341524695465728854562772615169 17773364155682651268275142113690502555604463656341524695465728795189144715265
Поскольку мы работаем в сфере Web3, давайте посмотрим, как это выглядит в шестнадцатеричном формате… и вау! Сразу бросается в глаза разделение переменных:
#JavaScript >Web3.utils.toHex('17773364155682651268275142113690502555604463656341524695465728811681819131905'); <'0x274b5e1c7257f1092402c2d2ffb987010a48496d000000000002a30000000001' // |------------- MAKER ADDRESS ----------|--- NFT ID --|-- AID --| >Web3.utils.toHex( '17773364155682651268275142113690502555604463656341524695465728854562772615169' ); <'0x274b5e1c7257f1092402c2d2ffb987010a48496d000000000002ca0000000001' // |------------- MAKER ADDRESS ----------|--- NFT ID --|-- AID --| >Web3.utils.toHex('17773364155682651268275142113690502555604463656341524695465728795189144715265'); <'0x274b5e1c7257f1092402c2d2ffb987010a48496d000000000002940000000001' // |------------- MAKER ADDRESS ----------|--- NFT ID --|-- AID --|
Реконструкция
Теперь мы знаем, как их разъединить… Можем ли мы собрать их вместе? В частности, если в нашей коллекции 620 элементов, как мы можем проверить элемент 211? Может быть, нам нужен напольный бот? Благодаря некоторому парному программированию с Mumbu, у нас есть эта функция JS:
#JavaScript function getOpenSeaTokenID( collectionAddress, tokenId, assocId ){ //convert back to hex, and strip '0x' prefix tokenId = Web3.utils.toHex( tokenId ).substring( 2 ); // need 14 hex digits for 7 bytes if( tokenId.length < 14 ){ tokenId = tokenId.padStart( 14, '0' ); } //convert back to hex, and strip '0x' prefix assocId = Web3.utils.toHex( assocId ).substring( 2 ); // need 10 hex digits for 5 bytes if( assocId.length < 14 ){ assocId = assocId.padStart( 10, '0' ); } const fullTokenId = collectionAddress + tokenId + assocId; return Web3.utils.hexToNumberString( fullTokenId ); };
А результаты… да, d3 = 211 в гексе!
#JavaScript >getOpenSeaTokenID( collectionAddress, 211, 1 ); <'17773364155682651268275142113690502555604463656341524695465728301508423843841' >Web3.utils.toHex('17773364155682651268275142113690502555604463656341524695465728301508423843841'); <'0x274b5e1c7257f1092402c2d2ffb987010a48496d000000000000d30000000001'
Другие реализации
В своем смарт-контракте CyberKongz использует маскирование и сдвиг битов для извлечения нужных им байтов. Это немного сбивает с толку, потому что они передают uint
(12345), а затем используют hex literal
(0x00ff) для маскировки; у нас есть некоторые умственное жонглирование, чтобы сделать.
uint
является десятичным числом (основание 10), и мы видели это с идентификаторами токенов ОС выше. Но десятичное число не выравнивается по границам байтов. От 0 до 255 — это 1 байт, от 256 до 65 535 — это 2-й байт… это не способ работы с данными. 😞 К счастью, каждые 2 шестнадцатеричных цифры составляют ровно 1 байт, и именно поэтому он выявил закономерность. Поэтому примите на веру, что целые числа имеют одинаковые двоичные данные, просто это плохой формат для людей, которым нужно видеть данные.
Учитывая, что они равны, мы можем увидеть, как работает маскирование:
#Solidity uint _id = 17773364155682651268275142113690502555604463656341524695465728301508423843841; //0x274b5e1c7257f1092402c2d2ffb987010a48496d000000000000d30000000001 uint isolatedTokenId = _id & 0x0000000000000000000000000000000000000000ffffffffffffff0000000000; //see how the bytes line up? // 00 means "ignore this" and ff means "keep this" uint isolatedTokenId = 0x274b5e1c7257f1092402c2d2ffb987010a48496d000000000000d30000000001 & 0x0000000000000000000000000000000000000000ffffffffffffff0000000000;
Затем мы используем битовый сдвиг, чтобы удалить 10 справа! (10 шестнадцатеричных = 5 байтов = 40 бит):
#Solidity // bit shift 40 bits "off" of the end of the number isolatedTokenId = isolatedTokenId >> 40;
Для solidity, python и других языков программирования нарезка может быть еще проще!
#Solidity bytes32 data = 0x274b5e1c7257f1092402c2d2ffb987010a48496d000000000000d30000000001; |------------- MAKER ADDRESS ----------|--- NFT ID --|-- AID --| //1. first 20 bytes are the maker address bytes owner = data[0:20]; //2. next 7 bytes are the nft ID bytes nftID = data[20:27]; //3. last 5 bytes the value associated to the ID bytes assocID = data[27:32]; #Python # load the integer: data = '{:x}'.format( 17773364155682651268275142113690502555604463656341524695465728301508423843841 ) # load a(r)aw (b)ytes literal: data = rb'0x274b5e1c7257f1092402c2d2ffb987010a48496d000000000000d30000000001' |------------- MAKER ADDRESS ----------|--- NFT ID --|-- AID --| //1. first 40 digits are the maker address owner = data[0:40]; //2. next 14 digits are the nft ID nftID = data[40:54] //3. last 10 digits the value associated to the ID assocID = data[54:64];
Удачного взлома!
- Squeebo