Solidity Blockchain Idcard


มาลองคิดลองเขียน smart contract กันหน่อยไหม ในตัวอย่างนี้ผมจะลองเก็บข้อมูลอะไรง่ายๆ ดู เพราะ blockchain มันก็คือการเก็บข้อมูลแบบกระจายศูนย์ มันคล้ายกับเป็น ฐานข้อมูล นั่นแหละ
เริ่ม
สร้างไฟล์ชื่อว่า Idcard.sol ในโฟลเดอร์ contract
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Idcard { // code...}
ประกาศ license กำหนดให้ไฟล์นี้ใช้ compiler version อะไรแล้วสร้าง contract ชื่อ Idcard
ใครคือเจ้าของ contract
address private admin;
สร้างตัวแปรชื่อ admin เอาไว้เก็บข้อมูลชนิด address ของเจ้าของ contract
บัตรของเราจะเก็บข้องมูลอะไรบ้าง
- เลขประจำตัว 13 หลัก
- ชื่อ
- นามสกุล
- วันเกิด คำนวณอายุได้
- วันที่ออกบัตร
- วันหมดอายุบัตร
- สถานะยืนยันตัวตนด้วยการ KYC
- ยืนยันตัวตนโดยใคร
- ยืนยันตัวตน ณ เวลา
mapping(address => uint) private id;mapping(uint => string) private name;mapping(uint => string) private lastname;mapping(uint => uint) private birthdate;mapping(uint => uint) private date_of_issure;mapping(uint => uint) private date_of_expire;mapping(uint => bool) private kyc;mapping(uint => address) private kycby;mapping(uint => uint) private inspec_at;
จากข้อมูลนี้ จะเห็นได้ว่ามีสถานะยืนยันตัวตนอยู่ด้วย เพื่อเอาไว้ตรวจสอบว่าข้อมูลบัตรชุดนี้มีความน่าเชื่อถือและคนที่ยืนยันได้จะต้องเป็นองค์กรเชื่อถือได้เท่านั้นเช่น องค์กรที่ได้รับการยอมรับจากรัฐอย่างธนาคารพาณิชย์หรือธนาคารเองนั้นแหละที่เป็นคนยืนยัน
mapping(address => bool) public list_inspectors;mapping(address => string) public inspectorsis;
เอาแบบคล่าวๆละกัน ทีนี้ก็ได้ข้อมูลในบัตรแล้วเรามากำหนดฟังก์ชันเพื่อให้ Application เรียกดูข้อมูลอะไรได้บ้าง
constructor ประมวลผลครั้งแรกครั้งเดียวเมื่อ deploy
constructor() { admin = msg.sender; list_inspectors[msg.sender] = true; inspectorsis[msg.sender] = "Developper";}
จากโค้ดใน constructor จะกำหนดความเป็นเจ้าของเพื่อนำไปกำหนดเงื่อนไขต่างๆภายใน contract เช่นฟังก์ชันที่ใช้ได้เฉพาะเจ้าของเท่านั้น
modifier ทำหน้าที่คล้าย middleware ฟังก์ชัน
modifier adminOnly { require(msg.sender == admin, "Forbidden"); _;}
modifier inspectorOnly { require(list_inspectors[msg.sender], "Forbidden"); _;}
modifier ฟังก์ชันนี้ถูกประมวลผลก่อนเมื่อผ่านหรือไม่เกิด error ก็จะประมวลผลโค้ดในส่วนของ body ในวงเล็บ นั้นแหละ ในฟังก์ชันนั้นๆเป็นลำดับถัดไป
ฟังก์ชันที่ Application จะเรียกดูข้อมูลบัตร
- สามารถดูเลขบัตรประจำตัว 13 หลักได้
- สามารถดูชื่อได้
- สามารถดูนามสกุลได้
- สามารถดูวันเกิดได้
- สามารถดูสถาณะยืนยันตัวตนได้
function getId() public view returns(uint) { return (id[msg.sender]);}function getName(uint _id13) public view returns(string memory) { return (name[_id13]);}function getLastname(uint _id13) public view returns(string memory) { return (lastname[_id13]);}function getBirthdate(uint _id13) public view returns(uint) { return (birthdate[_id13]);}function getIskyc(uint _id13) public view returns(bool) { return (kyc[_id13]);}
จะเห็นได้ว่าแต่ละฟังก์ชันจะดูข้อมูลได้แค่อย่างเดียว แต่จริงๆแล้ว solidity ก็มีความสามารถ return ค่าออกมาได้หลายๆค่าภายในฟังก์ชันเดียวนะ แล้วทำไมถึงต้องทำแยกฟังก์ชันแบบนี้ด้วย เหตุผลที่ผมทำแบบนี้คือเราสามารถกำหนดเงื่อนไขให้แต่ละฟังก์ชันว่าอนุญาตให้ใครเข้าถึงข้อมูลเหล่านี้ได้บ้าง
ฟังก์ชันที่ Application จะเรียกเพิ่มและแก้ไขข้อมูล
- สามารถเพิ่มผู้ที่ทำหน้าที่ตรวจสอบ KYC
- สามารถเพิ่มผู้ที่ทำหน้าที่ตรวจสอบ KYC
- สามารถยืนยันข้อมูลบัตรได้
- สามารถเพิ่มข้อมูลบัตรได้
- สามารถเพิ่มข้อมูลบัตรโดยผู้ตรวจสอบได้
function setInspector(address _inspector, bool _val, string memory _name) public adminOnly { list_inspectors[_inspector] = _val; inspectorsis[_inspector] = _name; // inspec_at[_inspector] = now;}
function setKyc(uint _id13, bool _val) public inspectorOnly { kyc[_id13] = _val; kycby[_id13] = msg.sender; inspec_at[_id13] = block.timestamp;}
function setAccount( uint _id13, string memory _name, string memory _lastname, uint _birthdate, uint _date_of_issure, uint _date_of_expire) public { require(id[msg.sender] == _id13, "You don't have permission");
id[msg.sender] = _id13; name[_id13] = _name; lastname[_id13] = _lastname; birthdate[_id13] = _birthdate; date_of_issure[_id13] = _date_of_issure; date_of_expire[_id13] = _date_of_expire;}
function setAccountByInspector( address _address, uint _id13, string memory _name, string memory _lastname, uint _birthdate, uint _date_of_issure, uint _date_of_expire) public inspectorOnly { require(_address != admin, "Forbidden");
id[_address] = _id13; name[_id13] = _name; lastname[_id13] = _lastname; birthdate[_id13] = _birthdate; date_of_issure[_id13] = _date_of_issure; date_of_expire[_id13] = _date_of_expire;}
จากโค้ดด้านบนอธิบายแต่ละฟังก์ชันได้ว่า
- setInspector เป็นฟังก์ชันที่ใช้ได้เฉพาะเจ้าของ contract เท่านั้น สามารถเพิ่มและแก้ไขรายชื่อผู้มีสิทธิในการยืนยันตัวตนได้
- setKyc เป็นฟังก์ชันที่ใช้ได้เฉพาะผู้มีสิทธิยืนยันตัวตนเท่านั้น เจ้าของก็มีสิทธินี้นะกำหนดไว้ใน constructor แล้ว สามารถยืนยันเพื่อให้มีความน่าเชื่อถือของข้อมูลบัตร
- setAccount เป็นฟังก์ชันที่ใครก็ใช้ได้ สามารถเพิ่มและแก้ไขข้อมูลบัตรของตนเองได้เท่านั้น
- setAccountByInspector เป็นฟังก์ชันที่ใช้ได้เฉพาะผู้มีสิทธิยืนยันตัวตนเท่านั้น สามารถเพิ่มและแก้ไขข้อมูลบัตรของใครก็ได้ยกเว้นเจ้าของ contract
จะเห็นได้ว่า มีช่องโหว่หรือ logic ที่ไม่ดีในหลายๆขั้นตอนเช่น
- Inspector สามารถแก้ไขข้อมูลของ Inspector คนอื่นได้
- ใครก็สามารถใส่เลขประจำตัว 13 หลักที่ไม่เป็นความจริงก็ได้ในครั้งแรก
- สามารถสร้าง account ใหม่แล้ว ใส่เลขประจำตัว 13 หลักซ้ำกับคนเก่าก็ได้
- ยังมีเยอะเลยครับ ลองคิดกันเล่นๆดูครับ สิ่งสำคัญคือเมื่อ deploy ไปแล้วเราไม่สามารถแก้ไขโค้ดได้อีกแล้ว Code Is Law
หากวาดการทำงานออกมาเป็นภาพจะได้
deply ใน remix ide
ขั้นตอนดังนี้
สุดท้าย ก็หวังว่าบทความนี้จะเป็นประโยชน์กับใครหลาย ๆ คนนะครับ