DEV Community

armariya
armariya

Posted on • Originally published at Medium on

วิธีใช้ ECDSA ในการเข้ารหัสและถอดรหัสบน Ethereum Smart contract เพื่อใช้เป็น Source ในการ Random

สวัสดีครับ วันนี้กลับมากลับซีรีส์ที่เขียนเกี่ยวกับ Ethereum นะครับผม สำหรับวันนี้คือเป็นสิ่งที่ผมเพิ่งเรียนรู้มาจากการทำงานนะครับ เรื่องนั้นก็คือ การใช้ ECDSA ในการ verify ตัวตนและใช้เป็น source ในการ random โดยที่ miner จะไม่สามารถรู้ค่าได้ก่อนนะครับ เราไปลุยกันเลย!

ECDSA คืออะไร?

เรามาเริ่มที่ตรงนี้กันก่อนครับ 555 ถ้าเรายังไม่รู้จักวิธีที่เราจะใช้ซะก่อน เราจะเรียนไปทำไมถูกมั้ยครับ :) สำหรับ ECDSA นะครับย่อมาจาก Elliptic Curve Digital Signature Algorithm เป็นอัลกอริทึมที่ Bitcoin กับ Ethereum ใช้ในการสร้างตัว Private Key, Public Key และ Address ที่เรา ๆ ใช้กันอยู่นั่นเองครับ นั่นเรื่องของรายละเอียดว่าทำงานยังไง อะไรยังไงนี่ เดี๋ยวไว้บทความถัด ๆ ไปนะครับเนื่องจากไม่งั้นจะยาวมาก (อาจจะไม่มีด้วย 555)

รูปตัวอย่างที่ใช้สำหรับในการคำนวณ Eliptic Curve

ซึ่งตัว ECDSA เป็นอัลกอริทึมสำหรับการสร้าง Private Key และ Public Key โดยการทำงานคร่าว ๆ คือเราสามารถเข้ารหัสด้วย Public Key หรือ Private Key ของเราได้ แต่จะสามารถถอดได้ด้วยคู่ Key ของตัวเองเท่านั้น เช่นถ้าเข้ารหัสด้วย Public Key ก็จะถอดด้วย Private Key ได้เท่านั้น หรือถ้าเข้ารหัสด้วย Private Key ก็จะถอดได้ด้วย Public Key ที่คู่กันเท่านั้น

เข้าเรื่อง !!

สำหรับสิ่งต่าง ๆ ที่จะใช้ในบทความนี้ จะมีดังนี้ครับ

Requirement

ถ้าใครมีหมดทั้งสามอย่างแล้วก็มาลุยกันเลยครับบ หรือถ้ายังไม่มีจะอ่านไปก่อนก็ได้ครับ แล้วค่อยมาทำตามทีหลังง ถ้าใครยังไม่ลงก็เข้าตามลิ้งได้เลยนะครับ ผมใส่ไว้ให้ตรง Requirement แล้วครับผม (​สำหรับบทความนี้จะเป็นบน macOS นะครับ สำหรับชาว Windows ต้องขออภัยมา ณ​ ที่นี้

มาเริ่มกันเลยครับ เริ่มจากเราจะทำการสร้าง Truffle Project ก่อนนะครับ ซึ่ง Truffle ก็คือ framework ในการทำโปรเจค Ethereum นะครับ​ ซึ่งจะช่วยให้เราสามารถ compile, test, deploy ได้ง่ายขึ้นมาก ๆ เริ่มจากทำการสร้าง folder ชื่อว่า ecdsa_test จากนั้นใช้ terminal แล้วเข้าไปที่ folder นั้น ๆ แล้วทำการ Init truffle project ด้วยคำสั่ง

truffle init
Enter fullscreen mode Exit fullscreen mode

จะได้ผลลัพธ์ออกมาแบบนี้นะครับ

■ ecdsa_test $ truffle init
Downloading…
Unpacking…
Setting up…
Unbox successful. Sweet!

Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
Enter fullscreen mode Exit fullscreen mode

จะเห็นว่ามีคำสั่งที่เราสามารถใช้ได้สามอันคือ

  • truffle compile ใช้สำหรับในการ compile contract เพื่อเตรียมพร้อมสำหรับการ Deploy
  • truffle migrate/deploy ใช้สำหรับการ deploy ขึ้นไปยัง network ของ Ethereum ซึ่งสำหรับในบทความนี้จะ deploy ขึ้น ไปบน private testnet ของเราเองนะครับ
  • truffle test จะรัน test ทั้งหมดใน folder test โดยสามารถเขียนด้วยภาษา javascript หรือ solidity ก็ได้ครับ

เดี๋ยวเราจะเริ่มเขียนที่ฝั่ง contract ก่อนนะครับ สร้างไฟล์ใน folder ที่ชื่อว่า contracts โดยมีชื่อไฟล์ว่า SimpleECDSA.sol นะครับ มี code ตามนี้

pragma solidity 0.4.24;

contract SimpleECDSA {
  address private publicKey = 0x831412;

  modifier mustSignWithECDSA(bytes32 hash, uint8 v, bytes32 r, bytes32 s) {
    require(ecrecover(hash, v, r, s) == publicKey);
    _;
  }

  function callWithECDSA(bytes32 hash, uint8 v, bytes32 r, bytes32 s) 
    public 
    view 
    mustSignWithECDSA(hash, v, r, s) 
    returns (uint8) 
  {
    return 1;
  }
}
Enter fullscreen mode Exit fullscreen mode

โอเคเดี๋ยวเราจะมาดูทีละบรรทัดกันครับว่าแต่ละอันมันทำอะไร

pragma solidity 0.4.24
Enter fullscreen mode Exit fullscreen mode

เป็นการบอกว่า file นี้จะใช้ solidity เวอร์ชันอะไร ซึ่งในที่นี้ใช้ 0.4.24 ครับ

contract SimpleECDSA {
  address publicKey = 0x831412;
Enter fullscreen mode Exit fullscreen mode

เป็นการประกาศว่าชื่อ contract คืออะไร และประกาศตัวแปรชื่อ publicKey เป็นประเภท address มีค่าเท่ากับ 0x83142

modifier mustSignWithECDSA(
  bytes32 hash, 
  uint8 v, 
  bytes32 r, 
  bytes32 s) 
{
  require(ecrecover(hash, v, r, s) == publicKey);
  _;
}
Enter fullscreen mode Exit fullscreen mode

เป็นการประกาศ modifier ที่ชื่อว่า mustSignWithECDSA และใช้ function ecrecover สำหรับถอดรหัสว่า signature ที่ส่งมาสามารถถอดมาได้ตรงกับ public key ของเราหรือไม่

function callWithECDSA(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
  public
  view
  mustSignWithECDSA(hash, v, r, s)
  returns (uint8)
{
  return 1;
}
Enter fullscreen mode Exit fullscreen mode

เป็น function ที่เอาไว้เทสตัว modifier ที่เราสร้างขึ้นเมื่อกี้ ถ้าเกิดว่าเรา verify ผ่านก็จะ return ค่า 1 กลับไป

เย่ ที่นี่เราก็เตรียมฝั่ง contract เสร็จเรียบร้อยแล้วนะครับ เดี๋ยวเราจะไปเขียนไฟล์ test กัน เอาไว้ใช้สำหรับลอง sign ด้วย private key จากทางฝั่ง javascript แล้วมาถอดที่ฝั่ง contract นะครับ

แต่ก่อนอื่นเราต้องสร้าง file ที่เอาไว้สำหรับ deploy ก่อนนะครับ สร้าง file ใน folder migration ชื่อว่า 2_deploy_contracts.js

// 2_deploy_contracts.js
const SimpleECDSA = artifacts.require('./SimpleECDSA.sol');

module.exports = function(deployer) {
  deployer.deploy(SimpleECDSA);
}
Enter fullscreen mode Exit fullscreen mode

สำหรับ file นี้ขอไม่อธิบายนะครับบ เป็นส่วน deploy ของ truffle framework จะขอข้ามไปนะครับ เพราะเริ่มยาวแล้ว 555

ต่อไปเราต้องทำการลง package เพิ่มกันก่อนสำหรับใช้ generate key ใน file test นะนะครับ ใช้คำสั่ง

nom init -y
npm install --save secp256k1
npm install --save web3
Enter fullscreen mode Exit fullscreen mode

โอเคครับ ถ้าลงเสร็จแล้วเรามาเริ่มเขียน file test กันเลยครับ ทำการสร้างไฟล์ที่ชื่อว่า simpleecdsa.js แล้วเขียน code ตามนี้ครับ

// simpleecdsa.js
const SimpleECDSA = artifacts.require('./SimpleECDSA.sol');
const { randomBytes } = require('crypto');
const secp256k1 = require('secp256k1');

contract('SimpleECDSA', async (accounts) => {
  let simpleECDSAInstance;

  before('setup contract instance', async () => {
    simpleECDSAInstance = await SimpleECDSA.new();
  });

  it('generate key', async () => {
    const Web3 = require('web3');
    const web3 = new Web3();
    const messageBuffer = new randomBytes(32);
    const messageHex = messageBuffer.toString('hex');
    let privateKey;
    do {
      privateKey = randomBytes(32);
    } while (!secp256k1.privateKeyVerify(privateKey));

    const privateKeyHex = `0x${privateKey.toString('hex')}`;
    const generatedAccount = web3.eth.accounts.privateKeyToAccount(privateKeyHex);
    console.log(generatedAccount);
  });
});
Enter fullscreen mode Exit fullscreen mode

สำหรับ test ตัวนี้เราจะเอาไว้สำหรับ generate key ขึ้นมาก่อนนะครับ จากนั้นค่อยมาเขียนตัว file test ที่แท้จริงกันครับ 555

เราถึงเวลาที่จะมา deploy กันแล้วครับ ตอนนี้เราจะต้องทำการเปิด private testnet ของตัวเองขึ้นมาซะก่อน มาถึงตรงนี้ถ้าใครยังไม่ได้ลง ganache-cli ลงซะตอนนี้เลยครับ ด้วยคำสั่ง

npm install -g ganache-cli
Enter fullscreen mode Exit fullscreen mode

พอลงเสร็จแล้วนะครับให้เปิด tab ใหม่บน terminal แล้วทำการรันคำสั่ง ganache-cli

ganache-cli
Enter fullscreen mode Exit fullscreen mode

ก็เรียบร้อยกันไปตอนนี้เราจะ private testnet อยู่บนเครื่องคอมพิวเตอร์ของเราแล้วนะครับที่ http://localhost:8545 นะครับ จากนั้นเราต้องทำการกำหนดค่าของ network ว่าจะให้ truffle deploy contract ของเราลงที่ไหนนะครับ เปิด file truffle.js ที่อยู่ใน project ขึ้นมาแล้วใส่ตามนี้ครับ

// truffle.js
module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*"
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

จากนั้นก็ทำการ compile แล้ว deploy ขึ้น private testnet ของเรากันครับ

truffle compile
truffle migrate
Enter fullscreen mode Exit fullscreen mode

การ compile ตัว truffle จะสร้าง folder ที่ชื่อ builds มาให้แล้วข้างในจะมี file ที่ compile เสร็จแล้วอยู่ในนั้น แล้วจากนั้นก็ทำการ migrate ขึ้น testnet ครับ ถ้าไม่มีอะไรผิดพลาด ก็จะได้แบบนี้ครับ จะเห็นว่ามีการเขียนว่า SimpleECDSA Saving successful migration to network…

รูปตอนที่ deploy สำเร็จ

ถ้าลองกลับไปดูที่ tab ของ ganache-cli จะเห็นว่ามี transaction ในการเรียกและ deploy contract ขึ้นมาครับ ถ้าไม่มี error ก็ฮูเร่ deploy สำเร็จแล้วครับ จากนั้นก็ลองรันเทสกันเลยครับ ด้วยคำสั่ง

truffle test
Enter fullscreen mode Exit fullscreen mode

รูปภาพของตัว account ที่ generate ขึ้นมาสำเร็จ

ถ้าเรา run test สำเร็จ เราจะได้ภาพข้างบนนี้ครับ จะเห็นว่าเรามี address, privateKey แล้วนะครับ ทีนี้เราจะเอาไปสองตัวนี้ไปใช้กันครับ โดยเราจะเก็บตัว address ไว้ใน contract ไว้ใช้สำหรับ verify กับ ecrecover ส่วน privateKey เราจะเก็บไว้ที่ test ไว้ใช้สำหรับ sign message นะครับ

ก่อนอื่นเราก็เอาไป address ไปเก็บที่ contract ก่อนเลยครับ โดยเอาไปแทนที่ตัวแปรที่ชื่อ publicKey ให้เป็นแบบนี้ครับ

address private publicKey = 0xACdbA4985df6b4A0C8AdD5DBCfe8360910E5E8b5;
Enter fullscreen mode Exit fullscreen mode

ส่วนใน file simpleecdsa.js จะทำการเพิ่มตัวแปร privateKey และแก้ให้กลายเป็น file test ที่แท้จริง โดยการเอา function generate ออกและเปลี่ยนเป็นการ sign message แทนนะครับ

// simpleecdsa.js
const SimpleECDSA = artifacts.require('./SimpleECDSA.sol');
const { randomBytes } = require('crypto');

contract('SimpleECDSA', async (accounts) => {
  let simpleECDSAInstance;
  let privateKey = '0x29a50358bd197f58314159af7e50718ff69c7181efa6f5c1b19ef78171082ef5';
  let web3;
  let generatedAccount;

  before('setup contract instance', async () => {
    simpleECDSAInstance = await SimpleECDSA.new();
    const Web3 = require('web3');
    web3 = new Web3(Web3.givenProvider || 'http://localhost:8545');
    generatedAccount = web3.eth.accounts.privateKeyToAccount(privateKey);  
});

  it('test sign randomMessage with ecdsa to generate signature use as a random source', async () => {
    const messageBuffer = randomBytes(32);
    const messageHex = messageBuffer.toString('hex');
    const signatureObject = generatedAccount.sign(messageHex);

    const result = await simpleECDSAInstance.callWithECDSA(
      signatureObject.messageHash,
      signatureObject.v,
      signatureObject.r,
      signatureObject.s
    );

    assert.equal(result, 1);
  });
});
Enter fullscreen mode Exit fullscreen mode

จากนั้นก็ทำการ compile, deploy, test เหมือนเดิมเลยครับ มีเพียงอย่างเดียวที่ต้องเปลี่ยนคือ

truffle deploy --reset
Enter fullscreen mode Exit fullscreen mode

ที่ต้องมี reset เพราะว่าไม่งั้น truffle จะจำไว้แล้วครับว่า SimpleECDSA เคย deploy ไปแล้ว truffle จะ deploy contract อันใหม่ขึ้นไปครับ พอ test เสร็จก็จะเจอความเขียวอันสวยงาม

รูปภาพตอน test สำเร็จ

สิ่งที่เกิดขึ้นก็คือใน contract ผมได้ทำการเอา address ไปเก็บไว้ก่อน เพื่อใช้เป็นตัว verify ส่วนใน file test ผมเอา private key ไปเก็บไว้สำหรับใช้ sign random message พอ sign เสร็จด้วยคำสั่ง generatedAccount.sign(privateKey) ปุ๊บจะได้เป็น signature object แล้วก็ทำการส่งไปซึ่งจะเห็นว่ามี messageHash, v, r, s ซึ่งใน ECDSA เนี่ย (r, s) ถือว่าเป็น signature ครับผม หลังจากที่ส่งไปฝั่ง Ethereum แล้วก็ทำการใช้คำสั่ง

ecrecover(hash, v, r, s)
Enter fullscreen mode Exit fullscreen mode

สำหรับการถอดรหัสออกมาว่าได้ Address ตรงกับที่เราประกาศไว้ใน contract รึเปล่านั่นเอง ซึ่งจากตรงนี้เราสามารถนำ r, s เนี่ยไปใช้เป็น random source ได้เลย ตัวอย่างเช่น

uint256 randomNumber = uint256(s) % 10000;
Enter fullscreen mode Exit fullscreen mode

แบบนี้เป็นต้น เย่ จบแล้ว!

อ๊ะลืมบอกไปนี่คือตัวอย่างหน้าตาของ r กับ s ครับ

r: 0x79650c78c66d36e4c1c7edde2c8d45e2db1f19710bbb596d3d60f043b6e87a0c
s: 0x309482307f62e1ca9cdf6017b5a911b2e99cf0443e78a286b7c81694bca0f33b
Enter fullscreen mode Exit fullscreen mode

เขียนไปเขียนมาเริ่มงงเอง ว่าตกลงเราได้อะไรจากบทความนี้กันแน่ 555 งั้นเดี๋ยวจะสรุปอีกทีละกันครับคือ

  • ได้วิธีการ generate key pair ด้วย ECDSA
  • ได้รู้การเข้า sign message ใน javascript และนำไปเช็คกับตัว Smart Contract ซึ่งช่วยสามารถให้ Verify ที่มาของคนแรกได้ว่าเป็นคนที่เรา approve รึเปล่า (เช่น ถ้าทำเกม casino งี้ เราก็ไม่อยากให้เขาโกง แต่เล่นผ่านเฉพาะหน้าเว็บเราใช่มั้ยครับ :))
  • ได้ random source ที่ miner ไม่สามารถเดาก่อนได้ (ไม่แน่ใจ แต่คิดว่าไม่ได้แล้วนะ ถ้ายังมีอีกช่วยบอกกันด้วยนะครับ 55)

ข้อเสีย

จากเมื่อกี้เหมือนจะดูดีนะ แต่ว่าข้อเสียของวิธีนี้เลยก็คือ (เขาเรียกวิธีนี้กัน signidice นะ search ดูได้) ด้วยความที่เรา randomMessage ใช่มั้ยครับ ฝั่งคนทำเว็บ เนี่ยสามารถแอบโกงได้ โดยการ randomMessage ไปเรื่อย ๆ จนกว่าจะได้เลขที่ทำให้คนเล่นแพ้ เช่น ถ้าเกมคือทอยลูกเต๋า ถ้าได้ > 3 ชนะ < 3 แพ้ ถ้าเจ้าของเกมจะโกง ก็แค่ randomMessage รัว ๆ จนกว่าจะได้ค่าที่ทำให้ signature ออกมา นำไป random แล้วมีค่า < 3 เท่านั้นเอง

สำหรับ code ที่เสร็จแล้ว สามารถดูได้ที่ https://github.com/qapquiz/ecdsa_test นี่เลยนะครับ

Oldest comments (0)