🔑 코인 투자 추천 링크 🔑
[NEW] 누구나 쉽게 따라하는 솔리디티 강의(솔리디티 버전 0.8.13)
5. 배열, 열거형(enum), 구조체(calldata,memory)
8. 이벤트(events), 생성자(constructor), 상속
10. 인터페이스(interface), payable, 이더전송,받기 관련
11. Fallback, Call, Delegate(솔리디티 업그레이드 기법)
12. 함수 선택자(function selector), 다른 컨트랙트 사용 및 생성기법
13. Try Catch, Import(임포트), Library(라이브러리)
14. ABI 디코드, hash 함수, 서명검증, 가스최적화
* 블록체인 전문가들도 놓치기 쉬운 비트코인, 이더리움의 핵심가치 강의
1. ABI 디코드
- abi.encode는 데이터를 bytes로 변환한다.
- abi.decode는 디코드 된 bytes를 원래 데이터로 변환한다.
전체소스
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract AbiDecode {
struct MyStruct {
string name;
uint[2] nums;
}
function encode(
uint x,
address addr,
uint[] calldata arr,
MyStruct calldata myStruct
) external pure returns (bytes memory) {
return abi.encode(x, addr, arr, myStruct);
}
function decode(bytes calldata data)
external
pure
returns (
uint x,
address addr,
uint[] memory arr,
MyStruct memory myStruct
)
{
// (uint x, address addr, uint[] memory arr, MyStruct myStruct) = ...
(x, addr, arr, myStruct) = abi.decode(data, (uint, address, uint[], MyStruct));
}
}
2. keccak256 해싱(hashing)
- keccak256은 keccak-256 해시 입력값을 계산
- 입력값으로부터 결정적인(deterministic) 유일한 ID 만들기
- 커밋공개방식(commit-Reveal scheme)
- 컴팩트 암호화 서명(Compact cryptographic signature) [ 더 큰 입력 대신에 hash 사인으로 대체)
- 예제) hash함수 사용예
예) 해시 함수 사용하여 만들어진 hash값 찾아내기.
3. 서명 검증하기
- 메세지는 체인에서 서명한 다음 스마트컨트랙트를 사용하여 체인에서 확인(검증)할 수 있음
- 예제소스)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/* Signature Verification
How to Sign and Verify
# Signing
1. Create message to sign
2. Hash the message
3. Sign the hash (off chain, keep your private key secret)
# Verify
1. Recreate hash from the original message
2. Recover signer from signature and hash
3. Compare recovered signer to claimed signer
*/
contract VerifySignature {
/* 1. Unlock MetaMask account
ethereum.enable()
*/
/* 2. Get message hash to sign
getMessageHash(
0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C,
123,
"coffee and donuts",
1
)
hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd"
*/
function getMessageHash(
address _to,
uint _amount,
string memory _message,
uint _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_to, _amount, _message, _nonce));
}
/* 3. Sign message hash
# using browser
account = "copy paste account of signer here"
ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log)
# using web3
web3.personal.sign(hash, web3.eth.defaultAccount, console.log)
Signature will be different for different accounts
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function getEthSignedMessageHash(bytes32 _messageHash)
public
pure
returns (bytes32)
{
/*
Signature is produced by signing a keccak256 hash with the following format:
"\x19Ethereum Signed Message\n" + len(msg) + msg
*/
return
keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)
);
}
/* 4. Verify signature
signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd
to = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C
amount = 123
message = "coffee and donuts"
nonce = 1
signature =
0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b
*/
function verify(
address _signer,
address _to,
uint _amount,
string memory _message,
uint _nonce,
bytes memory signature
) public pure returns (bool) {
bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce);
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
return recoverSigner(ethSignedMessageHash, signature) == _signer;
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
public
pure
returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig)
public
pure
returns (
bytes32 r,
bytes32 s,
uint8 v
)
{
require(sig.length == 65, "invalid signature length");
assembly {
/*
First 32 bytes stores the length of the signature
add(sig, 32) = pointer of sig + 32
effectively, skips first 32 bytes of signature
mload(p) loads next 32 bytes starting at the memory address p into memory
*/
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
// implicitly return (r, s, v)
}
}
4. 가스최적화
- memory 대신에 calldata를 사용하여 가스비 절감 가능
- 상태변수를 메모리로 불러서 사용하기
- for 반복문에서 i++를 ++i로 교체해서 사용
- 배열요소를 Cachig(캐싱)
- 예제소스)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// gas golf
contract GasGolf {
// start - 50908 gas
// use calldata - 49163 gas
// load state variables to memory - 48952 gas
// short circuit - 48634 gas
// loop increments - 48244 gas
// cache array length - 48209 gas
// load array elements to memory - 48047 gas
// uncheck i overflow/underflow - 47309 gas
uint public total;
// start - not gas optimized
// function sumIfEvenAndLessThan99(uint[] memory nums) external {
// for (uint i = 0; i < nums.length; i += 1) {
// bool isEven = nums[i] % 2 == 0;
// bool isLessThan99 = nums[i] < 99;
// if (isEven && isLessThan99) {
// total += nums[i];
// }
// }
// }
// gas optimized
// [1, 2, 3, 4, 5, 100]
function sumIfEvenAndLessThan99(uint[] calldata nums) external {
uint _total = total;
uint len = nums.length;
for (uint i = 0; i < len; ) {
uint num = nums[i];
if (num % 2 == 0 && num < 99) {
_total += num;
}
unchecked {
++i;
}
}
total = _total;
}
}
소스 출처
https://solidity-by-example.org/
블록체인 교육 문의는 아래 링크 참고 바랍니다.
댓글