BTC
ETH
HTX
SOL
BNB
ดูตลาด
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

zk-STARK: ตรวจสอบการคำนวณการต่อสู้อัตโนมัติที่ซับซ้อนบน Ethereum

DAOrayaki
特邀专栏作者
บทความนี้มีประมาณ 12818 คำ การอ่านทั้งหมดใช้เวลาประมาณ 19 นาที
ในโพสต์นี้ ฉันจะไม่ลงลึกเกินไปว่า STARK ทำงานอย่างไร แต่ฉันจะให้ข้อมูลภาพรวมเชิงปฏิบัติว่า
สรุปโดย AI
ในโพสต์นี้ ฉันจะไม่ลงลึกเกินไปว่า STARK ทำงานอย่างไร แต่ฉันจะให้ข้อมูลภาพรวมเชิงปฏิบัติว่า

ผู้เขียนต้นฉบับ: Killari

ชื่อเดิม: STARKs: การตรวจสอบการคำนวณ auto battler ที่ซับซ้อนบน Ethereum — ปรับขนาดเกมแบบกระจายศูนย์

การดำเนินการฟังก์ชั่นที่ซับซ้อนบน Ethereum นั้นเป็นสิ่งที่ไม่ควรทำเสมอมาและไม่ควรทำ การคำนวณบล็อคเชนนั้นมีราคาแพงมาก เนื่องจากโหนดทั้งหมดจำเป็นต้องทำการคำนวณแบบเดียวกันเพื่อตรวจสอบความถูกต้อง

StarkWare เป็นหนึ่งในบริการปรับขนาด Ethereum ที่พยายามปรับขนาด Ethereum โดยใช้การพิสูจน์ความรู้ของ STARK (Scalable Transparent Proof of Knowledge) ในโพสต์นี้ ฉันจะไม่ลงลึกเกินไปว่า STARK ทำงานอย่างไร แต่ฉันจะให้ข้อมูลภาพรวมเชิงปฏิบัติว่านำไปใช้อย่างไรในทางปฏิบัติ

บางครั้ง STARK นำหน้าด้วย "zk-" ซึ่งหมายถึง "การพิสูจน์ด้วยความรู้เป็นศูนย์" ซึ่งทำให้ STARK สามารถพิสูจน์บางสิ่งได้โดยไม่ต้องเปิดเผยข้อมูลทั้งหมดเกี่ยวกับสิ่งที่เราพยายามพิสูจน์ StarkWare กำลังทำงานเพื่อรวม ZK ไว้ในไปป์ไลน์ในภายหลัง แต่ตอนนี้พวกเขามุ่งเน้นไปที่ความสามารถในการปรับขยายเท่านั้น และไม่จำเป็นต้องมีความรู้เป็นศูนย์เพื่อบรรลุเป้าหมายนี้

บล็อกโพสต์ของ Vitalik เป็นภาพประกอบของการแสดง zk-SNARK ต่อผู้ใช้

Vitalik ให้ภาพรวมสั้น ๆ เกี่ยวกับ STARK ในบทความของเขา STARK ตอนที่ 1: การพิสูจน์ด้วยพหุนามอธิบายโดยสังเขปว่าวิทยาการเข้ารหัสลับเหล่านี้ทำงานอย่างไร นอกจากนี้ยังมี SNARK (ข้อโต้แย้งความรู้รวบรัดแบบไม่โต้ตอบ) ซึ่งบรรลุสิ่งที่คล้ายกับ STARK แต่แตกต่างกันเล็กน้อย หากคุณสนใจที่จะทำความเข้าใจ zk-SNARK มากกว่านี้ ฉันขอแนะนำกระดาษคำอธิบายนี้: หลักการและการนำ zk-SNARK ไปใช้ คำอธิบายที่เชื่อถือได้ของ Maksym Petkus Petkus อธิบายหลักการของ zk-SNARK โดยเริ่มจากคณิตศาสตร์ขั้นพื้นฐาน จากนั้นจึงค่อย ๆ เจาะลึกคณิตศาสตร์ที่ซับซ้อนมากขึ้น โดยยืนอยู่บนไหล่ของรุ่นก่อน ๆ ประสบการณ์การอ่านที่ยอดเยี่ยม

Petkus สรุป zk-SNARK ดังนี้:

ชื่อเรื่องรอง

STARK และเกมต่อสู้อัตโนมัติ

เกมต่อสู้อัตโนมัติ (เรียกอีกอย่างว่าเกมต่อสู้อัตโนมัติ, หมากรุกอัตโนมัติ) เป็นเกมประเภทหนึ่งที่ผู้เล่นเลือกให้ตัวละครเติบโต แต่ไม่สามารถเลือกได้ในระหว่างการต่อสู้ แต่ดำเนินต่อไปโดยอัตโนมัติ มันคงเป็นเรื่องสนุกที่จะมีเกมแบบนี้บน Ethereum L1 แต่ปัจจุบันเกมดังกล่าวไม่สามารถทำได้

ดังนั้น STARK จะนำประโยชน์อะไรมาสู่เกมการต่อสู้อัตโนมัติ การจำลองเกมการต่อสู้อัตโนมัตินั้นซับซ้อนมาก ดังนั้นจึงเป็นเรื่องยากที่จะเรียกใช้ หากมีเทคโนโลยีที่ทำให้มันทำงานเพียงครั้งเดียว และผู้เล่นคนอื่นสามารถไว้วางใจการคำนวณนี้ได้ ในขณะที่ต้องแน่ใจว่าไม่มีใครโกหก มันจะช่วยให้เราเล่นอัตโนมัติแบบกระจายศูนย์

นี่คือแผนปฏิบัติการ:

1) เขียนตรรกะการต่อสู้อัตโนมัติใน Python

2) สร้างตัวตรวจสอบในภาษาไคโรของ StarkNet (จำเป็นสำหรับการพิสูจน์ STARK)

3) รันโค้ดสำหรับการต่อสู้อัตโนมัติ โดยใช้ตัวตรวจสอบไคโรที่เราสร้างขึ้นเพื่อสร้างหลักฐานการทำงานของโปรแกรม STARK

ชื่อเรื่องรอง

เขียนระบบการต่อสู้อัตโนมัติใน Python

เพื่อเริ่มต้นการเดินทางของเรา เราเพียงตั้งโปรแกรมตรรกะของการต่อสู้อัตโนมัติตามปกติ ไม่ต้องใช้เวทมนตร์ STARK ที่นี่

เราสร้างเครื่องจำลองการต่อสู้อัตโนมัติระหว่างตัวละครสองตัว บทบาทเหล่านี้มีสี่สถานะ:

- สุขภาพดี. เท่ากับจำนวนความเสียหายทั้งหมดที่พวกเขาสามารถรับได้

@dataclass
class Character:
  health: int
  damage: int
  attackRecoverTime: int
  healthPerTurn: int

- อันตราย. การโจมตีแต่ละครั้งจะสร้างความเสียหายมากน้อยเพียงใด

- Attack Recovery Time: หลังจากการโจมตีแต่ละครั้ง ผู้โจมตีต้องรอนานเท่าใดก่อนที่จะโจมตีอีกครั้ง

- ค่าพลังชีวิตต่อรอบ: ค่าพลังชีวิตที่ตัวละครฟื้นต่อรอบ

คุณลักษณะเหล่านี้สามารถกำหนดเป็นคลาสใน Python ได้ดังที่แสดงด้านล่าง

นี่คือรายละเอียดว่าการต่อสู้จะดำเนินไปอย่างไร:

  • เรามีวงจรเกมที่วนรอบการต่อสู้

  • ในแต่ละรอบ เราจะฟื้นฟูพลังชีวิตให้กับตัวละครทั้งสองและทำให้พวกเขาเอาชนะกันให้เร็วที่สุดเท่าที่จะทำได้

  • เราจะทำให้ตัวละครเหล่านี้ต่อสู้จนตัวตาย หากตัวละครตัวใดตัวหนึ่งตาย หรือตัวละครทั้งสองตัวตาย การต่อสู้จะถือว่าจบลง

การเลือกคุณสมบัติเฉพาะของตัวละคร เป็นไปได้ที่จะมีสถานการณ์ที่การต่อสู้ไม่มีวันสิ้นสุด แต่ดำเนินต่อไปตลอดไป สิ่งนี้สามารถหลีกเลี่ยงได้ ตัวอย่างเช่น การจำกัดจำนวนรอบสูงสุดที่การต่อสู้ของเราสามารถอยู่ได้ จากนั้นจึงพิจารณาแก้ไขการรบด้วยการเสมอกัน ซึ่งไม่ได้ตั้งใจให้พิจารณาในโค้ดตัวอย่างของเรา

ในตอนท้ายของการจำลอง ลองกลับมาว่าแต่ละตัวละครมีสุขภาพเท่าไร และนับจำนวนรอบของการต่อสู้

นี่คือเวอร์ชันที่เขียนด้วยรหัส Python:

def simulateFight(character1, character2):
 currentTick = 0
 lastAttack = [0, 0]
    
 while 1:
     # Health regeneration
     character1.health += character1.healthPerTurn
     character2.health += character2.healthPerTurn
     
     # Player 1 hits player 2
     if (lastAttack[0] == character1.attackRecoverTime):
         character2.health -= character1.damage
         lastAttack[0] = 0
     else:
         lastAttack[0] = lastAttack[0] + 1
         
     # Player 2 hits player 1
     if (lastAttack[1] == character2.attackRecoverTime):
         character1.health -= character2.damage
         lastAttack[1] = 0
     else:
         lastAttack[1] = lastAttack[1] + 1
     
     if character1.health <= 0 or character2.health <= 0:
         # one of the characters have died
         break
     currentTick = currentTick + 1
     
 return {
     'endHealths': [character1.health, character2.health],
     'nCombatRounds': currentTick
 }

ต่อไป มาเลือกกันว่าใครจะมีการต่อสู้ครั้งยิ่งใหญ่นี้:

เราจะมีออเกอร์ที่มีแถบพลังชีวิตยาว (พลังชีวิต 1,000 หน่วย!) ที่สร้างความเสียหาย 48 ดาเมจต่อการโจมตี แต่รอ 80 ครั้งระหว่างการโจมตี ยักษ์ยังฟื้นฟูพลังชีวิตสามแต้มต่อเทิร์น

ฮีโร่ผู้ท้าชิงของเรามีพลังชีวิตต่ำกว่ามาก เพียง 240 พลังชีวิต ไม่มีการฟื้นฟูพลังชีวิตเลย และสร้างความเสียหายต่อการโจมตีน้อยกว่า (20) แต่ฮีโร่ของเราต้องรอเพียง 2 เทิร์นก่อนที่จะโจมตี อย่างน้อยพระเอกของเราก็เก่งกว่าผีปอบ! นี่คือฮีโร่ของเรา นี่คือการต่อสู้ของดาวิดและโกลิอัทอย่างแท้จริง

เราสามารถกำหนดบทบาทของเราในโค้ดได้ดังนี้

player1 = Character(1000, 48, 80, 3) # Ogre
player2 = Character(240, 20, 2, 0) # Hero

จากนั้นเราสามารถจำลองการต่อสู้ได้โดยการเรียกใช้ฟังก์ชันการต่อสู้

simulateFight(player1, player2

เราจะได้ผลลัพธ์ของการต่อสู้:

{
    "player1": {
     "stats": [
      2000,
      48,
      80,
      3
     ]
    },
    "player2": {
     "stats": [
      1240,
      20,
      2,
      0
     ]
    },
    "log": {
     "endHealths": [
      999,
      1096
     ],
     "nCombatRounds": 272
    }
}ZERO_HP_POINT = 1000
assert player1.damage < ZERO_HP_POINT
assert player2.damage < ZERO_HP_POINT
data = {
    'player1': {
     'stats': [ZERO_HP_POINT + player1.health, player1.damage, player1.attackRecoverTime, player1.healthPerTurn]
    },
    'player2': {
     'stats': [ZERO_HP_POINT + player2.health, player2.damage, player2.attackRecoverTime, player2.healthPerTurn]
    },
  'log': simulateFight(player1, player2)
}
data['log']['endHealths'] = [h + ZERO_HP_POINT for h in data['log']['endHealths']]
with open('combat-input.json', 'w') as outfile:
json.dump(data, outfile)

เราสามารถอ่านได้จากผลการต่อสู้ที่กินเวลา 272 รอบและการต่อสู้สิ้นสุดลงเพราะอสูรพ่ายแพ้ ฮีโร่ของเราชนะด้วยเลือด 96 แต้ม! นี่คือฮีโร่ของเรา

ต่อไปมาเก็บข้อมูลสำคัญที่จำเป็นทั้งหมดเกี่ยวกับการต่อสู้ไว้ในไฟล์ เพื่อที่เราจะสามารถพิสูจน์การต่อสู้ด้วยหลักฐาน STARK ได้ในภายหลัง สิ่งที่เราต้องการคืออินพุตของโปรแกรม (คำจำกัดความของผู้เล่น 1 และผู้เล่น 2) และเอาต์พุตของโปรแกรม (สุขภาพเมื่อสิ้นสุดและจำนวนรอบ) โปรแกรมตรวจสอบของเราไม่ต้องการโปรแกรมเลย!

เราจะเขียนตัวตรวจสอบความถูกต้องในไคโร ไคโรเป็นภาษาโปรแกรมสำหรับการเขียนโปรแกรมที่ตรวจสอบได้ หนึ่งในข้อกำหนดหลายประการของไคโรคืออินพุตทั้งหมดต้องไม่เป็นค่าลบ มันเป็นความโชคร้ายของผู้พัฒนาของเรา แต่พรของฮีโร่ของเรา สุขภาพของผีปอบกลับติดลบ นั่นเป็นจำนวนลบ :(

เพื่อหลีกเลี่ยงข้อจำกัดนี้ เราจะเพิ่ม 1,000 ให้กับค่าพลังชีวิตทั้งหมด และถ้าค่าพลังชีวิตของตัวละครต่ำกว่า 1,000 จะถือว่าตัวละครนั้นตาย เพื่อให้วิธีนี้ใช้ได้กับค่าความเสียหายของตัวละครที่แตกต่างกัน เราต้องตรวจสอบให้แน่ใจว่าค่าความเสียหายของตัวละครไม่เกิน 1,000 เพราะสุขภาพของใครบางคนอาจกลายเป็นลบอีกครั้ง นี่คือรหัส:

{
    "player1": {
     "stats": [
      2000,
      48,
      80,
      3
     ]
    },
    "player2": {
     "stats": [
      1240,
      20,
      2,
      0
     ]
    },
    "log": {
     "endHealths": [
      999,
      1096
     ],
     "nCombatRounds": 272
    }
}ZERO_HP_POINT = 1000
assert player1.damage < ZERO_HP_POINT
assert player2.damage < ZERO_HP_POINT
data = {
    'player1': {
     'stats': [ZERO_HP_POINT + player1.health, player1.damage, player1.attackRecoverTime, player1.healthPerTurn]
    },
    'player2': {
     'stats': [ZERO_HP_POINT + player2.health, player2.damage, player2.attackRecoverTime, player2.healthPerTurn]
    },
  'log': simulateFight(player1, player2)
}
data['log']['endHealths'] = [h + ZERO_HP_POINT for h in data['log']['endHealths']]
with open('combat-input.json', 'w') as outfile:
json.dump(data, outfile)

ชื่อเรื่องรอง

{
    "player1": {
     "stats": [
      2000,
      48,
      80,
      3
     ]
    },
    "player2": {
     "stats": [
      1240,
      20,
      2,
      0
     ]
    },
    "log": {
     "endHealths": [
      999,
      1096
     ],
     "nCombatRounds": 272
    }
}

การเขียนตัวตรวจสอบไคโร

ต่อไป เราต้องเขียนโปรแกรมตรวจสอบเพื่อให้สามารถ STARK การทำงานของโปรแกรม Python ของเราได้ โปรแกรมต้องสามารถตรวจสอบได้ว่า เมื่อพิจารณาจากแอตทริบิวต์ของโปรแกรมเล่นเริ่มต้น เมื่อรันโค้ดจำลอง โปรแกรมจำลองจะสร้างผลลัพธ์ที่แน่นอนที่แสดงในพื้นที่บันทึก เพื่อให้บรรลุเป้าหมายนี้ เราจะเปลี่ยนไปใช้การเขียนโปรแกรมในภาษาไคโร

เราจะกำหนดโครงสร้างบทบาทในไคโรในลักษณะที่คล้ายกับที่เราเห็นในไพธอน:

struct Character:
    member health: felt
    member damage: felt
    member attackRecoverTime: felt
    member healthPerTurn: felt
end

ข้อแตกต่างที่สำคัญเมื่อเทียบกับ Python คือเราจำเป็นต้องกำหนดตัวแปรทั้งหมดให้กับชนิดข้อมูลที่ไม่คุ้นเคย (ย่อมาจาก field element) ประเภทข้อมูลความรู้สึกเป็นประเภทตัวแปร 252 บิต (แปลกใช่ไหม) ที่กำหนดระหว่าง (-P/2, P/2) โดยที่ P เป็นจำนวนเฉพาะขนาดใหญ่ 252 บิต

แม้ว่ามันอาจดูแปลก แต่สำหรับจุดประสงค์ของเรารู้สึกว่ามันทำงานเหมือนจำนวนเต็ม ความสำคัญของข้อมูลประเภทนี้คือ คอมไพเลอร์ไคโรสามารถพิสูจน์ STARK จากโค้ดของเราได้ง่ายกว่าที่เราจะจัดการกับประเภทตัวแปรจำนวนเต็มหรือทศนิยม คุณสามารถอ่านเพิ่มเติมเกี่ยวกับประเภทตัวแปรสักหลาดได้จากเอกสารของไคโร

ตอนนี้เรามาเริ่มเขียนโปรแกรมไคโรเพื่อให้แน่ใจว่าการต่อสู้เป็นไปตามแผนและผู้ดำเนินการโปรแกรมไม่สามารถโกหกผู้เล่นคนอื่นได้ นอกจากประเภทสักหลาดและตัวแปรอินพุตที่ไม่เป็นลบแล้ว ไคโรยังมีข้อจำกัดอื่นๆ อีกบางประการ เราใช้การวนซ้ำแบบ while ในโค้ด Python แต่การวนซ้ำความยาวแบบไดนามิกไม่สามารถทำได้ในไคโร เราจำเป็นต้องเรียกซ้ำเพื่อใช้ตรรกะเดียวกัน ดังนั้น เรามากำหนดฟังก์ชันแบบเรียกซ้ำที่จะตรวจสอบการต่อสู้รอบเดียว จากนั้นตรวจสอบความถูกต้องของการต่อสู้รอบอื่นๆ ทั้งหมดซ้ำ:

func simulateCombat{ range_check_ptr } (
 player1: Character,
 player2: Character,
 currentHealths: (felt, felt),
 lastAttacks: (felt, felt),
 currentRound: felt,
 nCombatRounds: felt
) -> (
 simNextHealths: (felt, felt),
 simNextLastAttacks: (felt, felt)
)

ฟังก์ชั่นนี้จะนำตัวละครทั้งสองของเรา สุขภาพปัจจุบันและเวลาโจมตีครั้งสุดท้าย รอบการต่อสู้ปัจจุบัน และจำนวนรอบการต่อสู้เพื่อจำลอง จากนั้นเราจะส่งคืนค่าสุขภาพและตัวจับเวลาที่อัปเดตของการโจมตีครั้งล่าสุดหลังจากรอบการตรวจสอบ เพื่อให้สามารถตรวจสอบรอบถัดไปแบบวนซ้ำได้

คุณอาจสังเกตเห็น range_check_ptr ที่จุดเริ่มต้นของฟังก์ชันของเรา range_check_ptr เป็นตัวชี้ที่เราต้องใช้สำหรับการตรวจสอบช่วง (operator<, >, >=,<=). การดำเนินการเหล่านี้ยังทำได้ยากในไคโร แต่โชคดีที่เรายังสามารถทำได้โดยใช้ฟังก์ชันในตัว range_check_ptr ของไคโร

ต่อไปเรามาเริ่มกำหนดภายในของฟังก์ชันกัน โดยพื้นฐานแล้วเราใช้ตรรกะเดียวกันกับรอบการต่อสู้ของเราใน Python:

alloc_locals
local nextHealths: (felt, felt)
local nextLastAttacks: (felt, felt)
local ZERO_HP_POINT = 1000
if lastAttacks[1] == player2.attackRecoverTime:
 tempvar afterDamage = currentHealths[0] - player2.damage
 nextHealths[0] = afterDamage + player1.healthPerTurn
 nextLastAttacks[1] = 0
else:
 nextHealths[0] = currentHealths[0] + player1.healthPerTurn
 nextLastAttacks[1] = lastAttacks[1] + 1
end
if lastAttacks[0] == player1.attackRecoverTime:
 tempvar afterDamage = currentHealths[1] - player1.damage
 nextHealths[1] = afterDamage + player2.healthPerTurn
 nextLastAttacks[0] = 0
else:
 nextHealths[1] = currentHealths[1] + player2.healthPerTurn
 nextLastAttacks[0] = lastAttacks[0] + 1
end

โค้ดไคโรเขียนต่างกันเล็กน้อยเมื่อเทียบกับไพธอน นี่เป็นเพราะในไคโรเราสามารถใช้ตัวแปรคงที่เท่านั้น (ข้อจำกัดที่น่าสนใจอีกประการหนึ่ง) เมื่อเรากำหนดค่าให้กับตัวแปรแล้ว เราจะไม่สามารถเปลี่ยนแปลงเนื้อหาของมันได้อีกต่อไป ดังนั้น เมื่อเราเพิ่มพลังชีวิตของผู้เล่นสำหรับการฟื้นฟูพลังชีวิต เราจึงคำนวณพร้อมๆ กับความเสียหาย

ต่อไปเราจะตรวจสอบว่าเราได้จำลองจำนวนรอบที่ต้องการหรือไม่ หากเป็นเช่นนั้น เราจะยุติการจำลอง หากการจำลองยังไม่จบ เราต้องตรวจสอบว่าผู้เล่นทั้งสองยังมีชีวิตอยู่หรือไม่ หากเราไม่มีการตรวจสอบนี้ โปรแกรมจำลองการวิ่งที่เป็นอันตรายอาจจำลองการต่อสู้ต่อไปจนกว่าผู้เล่นทั้งสองคนจะตาย ซึ่งจะทำให้ผู้เล่นที่ตายไปแล้วสามารถต่อสู้ต่อไปได้! เราไม่สามารถปล่อยให้ความบ้าคลั่งเช่นนี้ เราไม่สามารถอนุญาตให้นักรบอันเดดปรากฏตัวในสนามประลองของเราได้!

if currentRound == nCombatRounds:
 return(nextHealths, nextLastAttacks)
else:
 # if the combat has not ended, nobody can be dead
 assert_nn(nextHealths[0] - ZERO_HP_POINT)
 assert_nn(nextHealths[1] - ZERO_HP_POINT)
end

ในที่นี้เราใช้ฟังก์ชัน assert_nn ซึ่งกำลังตรวจสอบว่าค่าภายในฟังก์ชันไม่เป็นค่าลบ: nextHealths[0] > ZERO_HP_POINT หากการยืนยันเป็นเท็จ การดำเนินการจะล้มเหลว และเรารู้ว่าผู้ที่เรียกใช้โปรแกรมจำลองนั้นโกหกเรา

สุดท้าย เรียกใช้ฟังก์ชันของเราเพื่อรับการจำลองรอบถัดไป จากนั้นเราจะส่งคืนผลการจำลองขั้นสุดท้าย

let (simulatedEndHealths, simulatedLastAttacks) = simulateCombat(
 player1 = player1,
 player2 = player2,
 currentHealths = nextHealths,
 lastAttacks = nextLastAttacks,
 currentRound = currentRound + 1,
 nCombatRounds = nCombatRounds
)
return(
 simNextHealths = simulatedEndHealths,
 simNextLastAttacks = simulatedLastAttacks
)

เมื่อเรามีรหัสสำหรับตรรกะการจำลองแล้ว เราต้องเขียนฟังก์ชันหลักสำหรับโปรแกรมไคโรของเรา โปรแกรมจำเป็นต้องอ่านเอาต์พุตของโปรแกรม Python จากนั้นจึงเรียกใช้ฟังก์ชัน simulatorCombat ที่เราเพิ่งสร้างขึ้น ฟังก์ชันนี้จะส่งคืนค่าความสมบูรณ์สุดท้าย ซึ่งเราต้องเปรียบเทียบกับเอาต์พุตของโปรแกรม Python นี่คือรหัส:

func main{ output_ptr: felt*, range_check_ptr }() -> ():
 alloc_locals
 local range_check_ptr = range_check_ptr
 local pl1: Character*
 local pl2: Character*
 local endHealths: felt*
 local nCombatRounds: felt
 %{
     log = program_input['log']
     dat_endHealths = log['endHealths']
     dat_nCombatRounds = log['nCombatRounds']
     
     ids.pl1 = pl1 = segments.add()
     for i, val in enumerate(program_input['player1']['stats']):
         memory[pl1 + i] = val
         
     ids.pl2 = pl2 = segments.add()
     for i, val in enumerate(program_input['player2']['stats']):
         memory[pl2 + i] = val
     ids.endHealths = endHealths = segments.add()
     for i, val in enumerate(dat_endHealths):
         memory[endHealths + i] = val
     ids.nCombatRounds = dat_nCombatRounds
     assert len(program_input['player1']['stats']) == 4
     assert len(program_input['player2']['stats']) == 4
     assert len(dat_endHealths) == 2
 %}
    
 local player1: Character = pl1[0]
 local player2: Character = pl2[0]
    
 local currentHealths: (felt, felt) = (player1.health, player2.health)
 local lastAttacks: (felt, felt) = (0, 0)
    
 let (simulatedEndHealths, lastSimulatedAttacks) = simulateCombat(
     player1 = player1,
     player2 = player2,
     currentHealths = currentHealths,
     lastAttacks = lastAttacks,
     currentRound = 0,
     nCombatRounds = nCombatRounds
 )
    
 # Check that the healths will match what was claimed
 assert simulatedEndHealths[0] = endHealths[0]
 assert simulatedEndHealths[1] = endHealths[1]
 # Return the program input and output
 serialize_word(player1.health)
 serialize_word(player1.damage)
 serialize_word(player1.attackRecoverTime)
 serialize_word(player1.healthPerTurn)
    
 serialize_word(player2.health)
 serialize_word(player2.damage)
 serialize_word(player2.attackRecoverTime)
 serialize_word(player2.healthPerTurn)
    
 serialize_word(simulatedEndHealths[0])
 serialize_word(simulatedEndHealths[1])
    
 return ()
end

ชื่อเรื่องรอง

ทำไมเราถึงต้องการสองโปรแกรม?

ชื่อเรื่องรอง

วิ่ง!

เราสามารถรัน Python emulator เพื่อคอมไพล์ Cairo validator ด้วย:

> python combat.py
> cairo-compile combat.cairo -- output combat-compiled.json

จากนั้น เราสามารถรันโปรแกรมไคโรด้วยไฟล์อินพุตที่เราสร้างด้วย combat.py โดยสร้างไฟล์ combat.pie:

> cairo-run -- program=combat-compiled.json -- program_input=combat-input.json -- layout=small -- cairo_pie_output=combat.pie

เรายังสามารถตรวจสอบได้ว่าไฟล์พายนั้นถูกต้องด้วยคำสั่งต่อไปนี้:

> cairo-run -- layout=small -- run_from_cairo_pie=combat.pie

คุณสามารถลองแก้ไขค่าสุขภาพที่ได้ในไฟล์ battle-input.json โปรดทราบว่าคุณไม่สามารถแก้ไขได้โดยไม่ได้รับข้อผิดพลาดในการตรวจสอบจากไคโร สำหรับแต่ละอินพุตเริ่มต้น (ผู้เล่น 1, ผู้เล่น 2 และจำนวนรอบ) การต่อสู้มีความละเอียดเดียวเท่านั้น ตัวอย่างเช่น หากเราพยายามโกงโดยให้แถบพลังชีวิตพิเศษแก่ฮีโร่ของเราเมื่อสิ้นสุดการต่อสู้ ผู้ตรวจสอบจะสังเกตว่าเรากำลังพยายามโกง

combat.cairo:120:5: Error at pc=0:201:
An ASSERT_EQ instruction failed: 1096 != 1097
assert simulatedEndHealths[1] = endHealths[1]
^*******************************************^

ไฟล์ .pie มีข้อมูลทั้งหมดที่ SHARP ต้องการในการสร้างการพิสูจน์สำหรับเรา SHARP เป็นบริการที่ดำเนินการโดย StarkWare ซึ่งสร้างการพิสูจน์ความถูกต้องของการดำเนินการโปรแกรมไคโร จากนั้นจะส่งหลักฐานเหล่านี้ไปยัง Ethereum testnet (Goerli) เพื่อตรวจสอบโดย Ethereum smart contracts

ปัจจุบัน SHARP ไม่สามารถรันในเครื่องได้ และวิธีเดียวที่จะสร้างการพิสูจน์จากโปรแกรมไคโรคือการใช้เซิร์ฟเวอร์ของ StarkWare เพื่อพิสูจน์ยืนยัน หวังว่าสิ่งนี้จะเปลี่ยนแปลงในอนาคตและทุกคนสามารถสร้างข้อพิสูจน์เกี่ยวกับอะไรก็ได้ StarkWare วางแผนที่จะปล่อยซอร์สโค้ดสำหรับ SHARP ในภายหลัง ปัจจุบันเกมแบบกระจายศูนย์ไม่สามารถสร้างโดยใช้ไคโรได้

ส่งผลงานมาที่ SHARP กันเถอะ:

> cairo-sharp submit -- cairo_pie combat.pie
Submitting to SHARP...
Job sent.
Job key: f48c551e-c0c6-4cf5-8a52-a0aa1c43728c
Fact: 0x17ee903dfb54b55e53cc03d5a47602e83ed0ff9e219fe4567b8d59fa2666f682

หลังจากนั้นไม่นาน เราควรเห็นว่าข้อเท็จจริงของเราได้รับการตรวจสอบและถูกต้องแล้ว:

> cairo-sharp is_verified 0x17ee903dfb54b55e53cc03d5a47602e83ed0ff9e219fe4567b8d59fa2666f682 -- node_url=https://goerli-light.eth.linkpool.io/
True

ชื่อเรื่องรอง

https://www.cairo-lang.org/playground/sharp.html?job_key=f48c551e-c0c6-4cf5-8a52-a0aa1c43728c

หลักฐานการใช้งานบนสัญญาสมาร์ท Ethereum

ตอนนี้เราทราบแล้วว่าสำหรับค่าอินพุตที่กำหนด (ผู้เล่น 1 ผู้เล่น 2 และจำนวนรอบ) เมื่อพิจารณาจากแฮชข้อเท็จจริง โปรแกรมควรแสดงค่าความสมบูรณ์สุดท้าย เราจึงสามารถตรวจสอบการคำนวณแบบออนไลน์ได้โดยไม่ต้อง ทำอีกครั้งบนเครือข่าย ทำการคำนวณ

อันที่จริง ยังมีข้อมูลอีกส่วนหนึ่งที่เราขาดหายไป ซึ่งก็คือแฮชของโปรแกรม

> cairo-hash-program -program combat-compiled.json
0x248070cb7b7f20b0b9445a382fdb4faa6b69c1f3653355077ae05b82c636ddf

นี่คือจุดสุดยอดของงานก่อนหน้าทั้งหมดของเรา ความมหัศจรรย์ของการเข้ารหัสสมัยใหม่ ฟังก์ชัน Solidity ที่เรียบง่ายที่ตรวจสอบข้อเท็จจริงและตรวจสอบว่าโปรแกรมเอาต์พุตนั้นถูกต้อง ทั้งหมดโดยไม่ต้องคำนวณเอง

function verifyCombatOutput(uint256[] memory programOutput) public
 bytes32 cairoProgramHash_ = 0x248070cb7b7f20b0b9445a382fdb4faa6b69c1f3653355077ae05b82c636ddf
 // Ensure that a corresponding proof was verified.
 bytes32 outputHash = keccak256(abi.encodePacked(programOutput));
 bytes32 fact = keccak256(abi.encodePacked(cairoProgramHash_, outputHash));
 require(cairoVerifier_.isValid(fact), "MISSING_CAIRO_PROOF");
 // Ensure the output consistency with current system state.
 require(programOutput.length == 10, "INVALID_PROGRAM_OUTPUT");
 require(player1.health == programOutput[0], "INVALID_PROGRAM_OUTPUT 0");
 require(player1.damage == programOutput[1], "INVALID_PROGRAM_OUTPUT 1");
 require(player1.attackRecoverTime == programOutput[2], "INVALID_PROGRAM_OUTPUT 2");
 require(player1.healthPerTurn == programOutput[3], "INVALID_PROGRAM_OUTPUT 3");
    
 require(player2.health == programOutput[4], "INVALID_PROGRAM_OUTPUT 4");
 require(player2.damage == programOutput[5], "INVALID_PROGRAM_OUTPUT 5");
 require(player2.attackRecoverTime == programOutput[6], "INVALID_PROGRAM_OUTPUT 6");
 require(player2.healthPerTurn == programOutput[7], "INVALID_PROGRAM_OUTPUT 7");
    
 // Now we know that programOutput[8] and programOutput[9] represent the resulting health values,
 // given the initial combat state variables programOutput[0..7]!.
    
 // Pure Magic.
}

ที่นี่ cairoVerifier._isValid(ข้อเท็จจริง) เป็นสัญญาของ Goerli

https://goerli.etherscan.io/address/0xAB43bA48c9edF4C2C4bB01237348D1D7B28ef168

ซอร์สโค้ดสำหรับไฟล์ในบทความอยู่ที่นี่: https://github.com/KillariDev/STARK-Combat

ETH
Layer 2
ยินดีต้อนรับเข้าร่วมชุมชนทางการของ Odaily
กลุ่มสมาชิก:
https://t.me/Odaily_News
กลุ่มสนทนา:
https://t.me/Odaily_GoldenApe
บัญชีทางการ:
https://twitter.com/OdailyChina
สารบัญบทความ
คลังบทความของผู้เขียน
DAOrayaki
ดาวน์โหลดแอพ Odaily พลาเน็ตเดลี่
ให้คนบางกลุ่มเข้าใจ Web3.0 ก่อน
IOS
Android