- Published on
EIP-712 Encoding
- Authors
- Name
- Dirga Yasa

Kenapa Hashing EIP-712
EIP-712 adalah standar untuk melakukan hashing dan signing untuk sebuah struktur object. Pada database seperti mysql biasanya kita menggunakan id integer autoincrement atau uuid pada setiap record yang tersimpan. Namun smart contract memiliki keterbatasan penyimpanan dan assign id manual untuk setiap record yang tersimpan akan menghabiskan banyak gas fee. Jadi kali ini kita akan membahas bagaimana hashing EIP-712 dapat digunakan sebagai id untuk record yang tersimpan.
Single Struct
Solidity
Hal pertama yang perlu dilakukan adalah membuat struct di solidity
struct Offer {
address owner;
string name;
uint256 price;
}Kemudian membuat typehash. Typehash single object dibuat dengan cara hashing representasi type dari struct. Berikut adalah representasi type dari struct tersebut
'Offer(address owner,string name,uint256 price)'Jangan menambahkan spasi pada type
Kemudian di-hash menggunakan fungsi keccak256()
bytes32 constant OFFER_TYPEHASH = keccak256("Offer(address owner,string name,uint256 price)")Kemudian lakukan encoding pada value yang akan di-hash
function hashOffer(Offer memory offer) external pure returns(bytes32) {
bytes32 OFFER_TYPEHASH = keccak256(bytes('Offer(address owner,string name,uint256 price)'));
bytes memory encoded = abi.encode(
OFFER_TYPEHASH,
offer.owner,
keccak256(bytes(offer.name)),
offer.price
);
return keccak256(encoded);
}Proses encoding dilakukan menggunakan fungsi abi.encode(). Hal yang di-encode adalah typehash, kemudian data sesuai urutan pada struct. Untuk data dengan type string harus dilakukan hashing terlebih dahulu. abi.encode() akan menghasilkan data bytes yang artinya panjangnya dynamic. Setelah di-encode, lakukan hashing dari bytes hasil encode sehingga menghasilkan hash untuk object Offer
Javascript
Untuk melakukan verifikasi off-chain, kita juga perlu melakukan hal yang sama pada bahasa pemrograman yang beriteraksi dengan blockhain. Umumnya bahasa yang digunakan adalah javascript dengan library ethers
Hal pertama adalah membuat typehash. Typehash tetap menggunakan representasi yang sama yang digunakan sebelumnya
'Offer(address owner,string name,uint256 price)'import ethers from 'ethers'
const typehash = ethers.keccak256(Buffer.from('Offer(address owner,string name,uint256 price)'))Kemudian hash object.
import ethers from 'ethers'
const offer = {
owner: '0x16f750B6bb0eeF814358773197812f2989efEEe2',
name: 'Water Bottle',
price: 1000
}
const hashedName = ethers.keccak256(Buffer.from(offer.name))
const encoded = coder.encode(
['bytes32', 'address', 'bytes32', 'uint256'],
[typehash, offer.owner, hashedName, offer.price]
)
const hash = ethers.keccak256(Buffer.from(encoded.slice(2), 'hex'))Ketika dijalankan akan menghasilkan hash 0x990fc339382823551c75f369768a7dae49725291532cb251e3cc64190f7e81f1.
Untuk data string, perlu untuk di-hash terlebih dahulu. coder.encode() memerlukan 2 parameter yaitu array of type dan array of value. Karena encode menghasilkan string hex, maka perlu sebelum di-hash, '0x' diawal perlu dihilangkan dan 'hex' param pada Buffer.from sebagai tanda bahwa string yang akan diubah menjadi buffer tersebut sudah berformat hex.
Nested Struct
Solidity
Sama seperti sebelumnya, buat struct solidity
struct Seller {
address owner;
string productName;
}
struct Buyer {
address owner;
uint256 price
}
struct Trade {
Seller seller;
Buyer buyer;
uint256 timestamp;
}Kemudian membuat typehash. Typehash pada nested object harus mencantumkan semua representasi type dan diurutkan sesuai abjad. Berikut adalah representasi type dari nested struct tersebut.
'Trade(Seller seller,Buyer buyer,uint256 timestamp)Buyer(address owner,uint256 price)Seller(address owner,string productName)'Kemudian di-hash menggunakan fungsi keccak256()
bytes32 TRADE_TYPEHASH = keccak256(bytes('Trade(Seller seller,Buyer buyer,uint256 timestamp)Buyer(address owner,uint256 price)Seller(address owner,string productName)'));Kemudian lakukan encoding pada value yang akan di-hash
function hashTrade(Trade memory trade) external pure returns(bytes32) {
bytes32 TRADE_TYPEHASH = keccak256(bytes('Trade(Seller seller,Buyer buyer,uint256 timestamp)Buyer(address owner,uint256 price)Seller(address owner,string productName)'));
bytes memory encodedSeller = abi.encode(
trade.seller.owner,
keccak256(bytes(trade.seller.productName))
);
bytes memory encodedBuyer = abi.encode(
trade.buyer.owner,
trade.buyer.price
);
bytes memory encodedTrade = abi.encode(
TRADE_TYPEHASH,
keccak256(encodedSeller),
keccak256(encodedBuyer),
trade.timestamp
);
return keccak256(encodedTrade);
}Untuk nested object, lakukan encode dimulai dari object paling dasar. Kemudian hasil encode tersebut di-hash dan dimasukkan kedalam encode object yang lebih besar.
Javascript
Untuk nested object, prosesnya hampir mirip dengan single object. Hal pertama adalah membuat typehash. Typehash tetap menggunakan representasi yang sama yang digunakan sebelumnya
'Trade(Seller seller,Buyer buyer,uint256 timestamp)Buyer(address owner,string productName)Seller(address owner,uint256 price)'import ethers from 'ethers'
const typehash = ethers.keccak256(Buffer.from('Trade(Seller seller,Buyer buyer,uint256 timestamp)Buyer(address owner,uint256 price)Seller(address owner,string productName)'))Kemudian hash object
import ethers from 'ethers'
const coder = ethers.AbiCoder.defaultAbiCoder();
const trade = {
seller: {
owner: '0x437C9Fe9c8C85Dd704541384528559D43590efB6',
productName: 'Water Bottle'
},
buyer: {
owner: '0x459f3A0143A5798d511E0A644655F43AE1abD2f9',
price: 1000
},
timestamp: 17370835505
}
// Encode Seller
const sellerHashedProductName = ethers.keccak256(Buffer.from(trade.seller.productName))
const sellerEncoded = coder.encode(
['address', 'bytes32'],
[trade.seller.owner, sellerHashedProductName]
)
const sellerHash = ethers.keccak256(Buffer.from(sellerEncoded.slice(2), 'hex'))
// Encode Buyer
const buyerEncoded = coder.encode(
['address', 'uint256'],
[trade.buyer.owner, trade.buyer.price]
)
const buyerHash = ethers.keccak256(Buffer.from(buyerEncoded.slice(2), 'hex'))
// Encode Trade
const tradeEncoded = coder.encode(
['bytes32', 'bytes32', 'bytes32', 'uint256'],
[typehash, sellerHash, buyerHash, trade.timestamp]
)
const hash = ethers.keccak256(Buffer.from(tradeEncoded.slice(2), 'hex'))Ketika dijalankan akan menghasilkan hash 0x6d0fdc0d54321da6983d7866f54dbfbe5c13c15a4b603729680118ea7110687a.
Jadi hashing struct/object menggunakan standar EIP-712 dapat digunakan untuk generate unique id untuk struct record yang tersimpan pada smart contract sehingga bisa menghemat storage dan gas fee untuk mapping sebuah id dengan sebuah struct record
Semoga bermanfaat 😃
Cheers.