zk-STARK: ตรวจสอบการคำนวณการต่อสู้อัตโนมัติที่ซับซ้อนบน Ethereum
ผู้เขียนต้นฉบับ: 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


