Formális ellenőrző rendszer létrehozása a semmiből. 1. rész: Karakteres virtuális gép PHP-ben és Pythonban

A formális ellenőrzés egy program vagy algoritmus ellenőrzése egy másikkal.

Ez az egyik leghatékonyabb módszer, amely lehetővé teszi a program összes sérülékenységének megtalálását, vagy annak bizonyítását, hogy azok nem léteznek.

A formai ellenőrzés részletesebb leírása a probléma megoldásának példáján látható Farkas, kecske és káposzta előző cikkemben.

Ebben a cikkben a problémák formális ellenőrzéséről áttérek a programokra, és leírom, hogyan
hogyan alakíthatók át automatikusan formális szabályrendszerekké.

Ehhez megírtam egy virtuális gép saját analógját, szimbolikus elveket használva.

Feldolgozza a programkódot és lefordítja egyenletrendszerré (SMT), ami már programozottan is megoldható.

Mivel a szimbolikus számításokkal kapcsolatos információk meglehetősen töredékesen jelennek meg az interneten,
Röviden leírom, hogy mi az.

A szimbolikus számítás a program egyidejű végrehajtásának módja az adatok széles skáláján, és a formális programellenőrzés fő eszköze.

Például beállíthatunk olyan bemeneti feltételeket, ahol az első argumentum bármilyen pozitív értéket vehet fel, a második negatív, a harmadik nulla, a kimeneti argumentum pedig például 42.

A szimbolikus számítások egy menetben megadják a választ arra, hogy el tudjuk-e érni a kívánt eredményt, és egy példát az ilyen bemeneti paraméterek halmazára. Vagy annak bizonyítéka, hogy nincsenek ilyen paraméterek.

Sőt, a bemeneti argumentumokat az összes lehetségesre állíthatjuk, és csak a kimeneti argumentumokat választhatjuk ki, például a rendszergazdai jelszót.

Ebben az esetben megtaláljuk a program összes sebezhetőségét, vagy bizonyítékot kapunk arra, hogy a rendszergazda jelszava biztonságos.

Megjegyezhető, hogy egy program klasszikus végrehajtása meghatározott bemeneti adatokkal a szimbolikus végrehajtás speciális esete.

Ezért a karakteres virtuális gépem egy szabványos virtuális gép emulációs módjában is működhet.

Az előző cikkhez fűzött megjegyzésekben találunk méltányos kritikát a formális verifikációval kapcsolatban, annak gyengeségeinek megvitatásával.

A fő problémák a következők:

  1. Kombinatorikus robbanás, mivel a formális ellenőrzés végső soron P=NP
  2. A fájlrendszerre, hálózatokra és egyéb külső tárolókra irányuló hívások feldolgozása nehezebb ellenőrizni
  3. Hibák a specifikációban, amikor a megrendelő vagy a programozó szándékozott egy dolgot, de nem írta le elég pontosan a műszaki leírásban.

Ennek eredményeként a program ellenőrizve lesz, és megfelel a specifikációnak, de teljesen mást fog csinálni, mint amit az alkotók vártak tőle.

Mivel ebben a cikkben elsősorban a formális verifikáció gyakorlati alkalmazására gondolok, egyelőre nem verem a falba a fejem, hanem olyan rendszert választok, ahol minimális az algoritmus bonyolultsága és a külső hívások száma.

Mivel az intelligens szerződések felelnek meg legjobban ezeknek a követelményeknek, a választás a Waves platform RIDE-szerződéseire esett: ezek nem Turing-teljesek, és maximális összetettségük mesterségesen korlátozott.

De ezeket kizárólag a technikai oldalról fogjuk figyelembe venni.

Minden szerződésnél mindenekelőtt különösen igény lesz a formai ellenőrzésre: a szerződéskötési hibát általában nem lehet kijavítani annak elindítása után.
És az ilyen hibák költsége nagyon magas lehet, mivel gyakran meglehetősen nagy összegeket tárolnak az intelligens szerződésekben.

A szimbolikus virtuális gépem PHP és Python nyelven íródott, és a Microsoft Research Z3Prover-jét használja a kapott SMT-képletek megoldására.

Lényege egy erőteljes, több tranzakcióra kiterjedő keresés, amely
lehetővé teszi megoldások vagy sebezhetőségek keresését, még akkor is, ha sok tranzakciót igényel.
még Mythrill, amely az egyik legerősebb szimbolikus keretrendszer az Ethereum sebezhetőségeinek feltárására, csak néhány hónapja adta hozzá ezt a képességet.

De érdemes megjegyezni, hogy az éterszerződések összetettebbek és a Turing teljesebbek.

A PHP a RIDE intelligens szerződés forráskódját python szkriptbe fordítja le, amelyben a program Z3 SMT-kompatibilis szerződési állapotok és azok átmeneti feltételeinek rendszereként jelenik meg:

Formális ellenőrző rendszer létrehozása a semmiből. 1. rész: Karakteres virtuális gép PHP-ben és Pythonban

Most részletesebben leírom, mi történik odabent.

De először néhány szó a RIDE intelligens szerződési nyelvről.

Ez egy funkcionális és kifejezés-alapú programozási nyelv, amely lusta kialakítású.
A RIDE elszigetelten fut a blokkláncon belül, és információkat tud lekérni és írni a felhasználó pénztárcájához kapcsolódó tárhelyről.

Minden pénztárcához csatolhat RIDE szerződést, és a végrehajtás eredménye csak IGAZ vagy HAMIS lesz.

Az IGAZ azt jelenti, hogy az intelligens szerződés lehetővé teszi a tranzakciót, a HAMIS pedig azt, hogy tiltja azt.
Egy egyszerű példa: egy szkript megtilthatja az átutalást, ha a pénztárca egyenlege kevesebb, mint 100.

Példaként ugyanazt a Farkast, Kecsket és Káposztát veszem, de már okos szerződés formájában bemutatva.

A felhasználó nem tud pénzt felvenni abból a pénztárcából, amelyre a szerződés vonatkozik, amíg mindenkit át nem küldött a másik oldalra.

#Извлекаем положение всех объектов из блокчейна
let contract = tx.sender
let human= extract(getInteger(contract,"human"))
let wolf= extract(getInteger(contract,"wolf"))
let goat= extract(getInteger(contract,"goat"))
let cabbage= extract(getInteger(contract,"cabbage"))

#Это так называемая дата-транзакция, в которой пользователь присылает новые 4 переменные.
#Контракт разрешит её только в случае если все объекты останутся в сохранности.
match tx {
case t:DataTransaction =>
   #Извлекаем будущее положение всех объектов из транзакции
   let newHuman= extract(getInteger(t.data,"human")) 
   let newWolf= extract(getInteger(t.data,"wolf"))
   let newGoat= extract(getInteger(t.data,"goat"))
   let newCabbage= extract(getInteger(t.data,"cabbage"))
   
   #0 обозначает, что объект на левом берегу, а 1 что на правом
   let humanSide= human == 0 || human == 1
   let wolfSide= wolf == 0 || wolf == 1
   let goatSide= goat == 0 || goat == 1
   let cabbageSide= cabbage == 0 || cabbage == 1
   let side= humanSide && wolfSide && goatSide && cabbageSide

   #Будут разрешены только те транзакции, где с козой никого нет в отсутствии фермера.
   let safeAlone= newGoat != newWolf && newGoat != newCabbage
   let safe= safeAlone || newGoat == newHuman
   let humanTravel= human != newHuman 

   #Способы путешествия фермера туда и обратно, с кем-то либо в одиночку.
   let t1= humanTravel && newWolf == wolf + 1 && newGoat == goat && newCabbage == cabbage 
   let t2= humanTravel && newWolf == wolf && newGoat == goat + 1 && newCabbage == cabbage
   let t3= humanTravel && newWolf == wolf && newGoat == goat && newCabbage == cabbage + 1
   let t4= humanTravel && newWolf == wolf - 1 && newGoat == goat && newCabbage == cabbage
   let t5= humanTravel && newWolf == wolf && newGoat == goat - 1 && newCabbage == cabbage
   let t6= humanTravel && newWolf == wolf && newGoat == goat && newCabbage == cabbage - 1
   let t7= humanTravel && newWolf == wolf && newGoat == goat && newCabbage == cabbage
   let objectTravel = t1 || t2 || t3 || t4 || t5 || t6 || t7
   
   #Последняя строка в разделе транзакции описывает разрешающее транзакцию условие.
   #Переменные транзакции должны иметь значения 1 или 0, все объекты должны
   #быть в безопасности, а фермер должен переплывать реку в одиночку 
   #или с кем-то на каждом шагу
   side && safe && humanTravel && objectTravel
case s:TransferTransaction =>
   #Транзакция вывода средств разрешена только в случае если все переплыли на другой берег
   human == 1 && wolf == 1 && goat == 1 && cabbage == 1

#Все прочие типы транзакций запрещены
case _ => false

}

A PHP először kivonja az összes változót az intelligens szerződésből a kulcsaik és a megfelelő logikai kifejezési változó formájában.

cabbage: extract ( getInteger ( contract , "cabbage" ) )
goat: extract ( getInteger ( contract , "goat" ) )
human: extract ( getInteger ( contract , "human" ) )
wolf: extract ( getInteger ( contract , "wolf" ) )
fState: human== 1 && wolf== 1 && goat== 1 && cabbage== 1
fState: 
wolf: 
goat: 
cabbage: 
cabbageSide: cabbage== 0 || cabbage== 1
human: extract ( getInteger ( contract , "human" ) )
newGoat: extract ( getInteger ( t.data , "goat" ) )
newHuman: extract ( getInteger ( t.data , "human" ) )
goatSide: goat== 0 || goat== 1
humanSide: human== 0 || human== 1
t7: humanTravel && newWolf== wolf && newGoat== goat && newCabbage== cabbage
t3: humanTravel && newWolf== wolf && newGoat== goat && newCabbage== cabbage + 1
t6: humanTravel && newWolf== wolf && newGoat== goat && newCabbage== cabbage - 1
t2: humanTravel && newWolf== wolf && newGoat== goat + 1 && newCabbage== cabbage
t5: humanTravel && newWolf== wolf && newGoat== goat - 1 && newCabbage== cabbage
t1: humanTravel && newWolf== wolf + 1 && newGoat== goat && newCabbage== cabbage
t4: humanTravel && newWolf== wolf - 1 && newGoat== goat && newCabbage== cabbage
safeAlone: newGoat != newWolf && newGoat != newCabbage
wolfSide: wolf== 0 || wolf== 1
humanTravel: human != newHuman
side: humanSide && wolfSide && goatSide && cabbageSide
safe: safeAlone || newGoat== newHuman
objectTravel: t1 || t2 || t3 || t4 || t5 || t6 || t7

A PHP ezután a Pythonban Z3Prover SMT-kompatibilis rendszerleírássá konvertálja őket.
Az adatok körbe vannak csomagolva, ahol a tárolási változók az i indexet, a tranzakciós változók az i + 1 indexet kapják, a kifejezésekkel rendelkező változók pedig meghatározzák az előző állapotból a következőbe való átmenet szabályait.

Ez a virtuális gépünk szíve, amely több tranzakciós keresési mechanizmust biztosít.

fState:  And( And( And( human[Steps]  ==  1 , wolf[Steps]  ==  1 )  , goat[Steps]  ==  1 )  , cabbage[Steps]  ==  1 )  
final:  fState[Steps] 
fState:   
wolf:   
goat:   
cabbage:   
cabbageSide:  Or( cabbage[i]  ==  0 , cabbage[i]  ==  1 )  
goatSide:  Or( goat[i]  ==  0 , goat[i]  ==  1 )  
humanSide:  Or( human[i]  ==  0 , human[i]  ==  1 )  
t7:  And( And( And( humanTravel[i] , wolf  ==  wolf[i] )  , goat[i+1]  ==  goat[i] )  , cabbage  ==  cabbage[i] )  
t3:  And( And( And( humanTravel[i] , wolf  ==  wolf[i] )  , goat[i+1]  ==  goat[i] )  , cabbage  ==  cabbage[i] + 1 )  
t6:  And( And( And( humanTravel[i] , wolf  ==  wolf[i] )  , goat[i+1]  ==  goat[i] )  , cabbage  ==  cabbage[i] - 1 )  
t2:  And( And( And( humanTravel[i] , wolf  ==  wolf[i] )  , goat[i+1]  ==  goat[i] + 1 )  , cabbage  ==  cabbage[i] )  
t5:  And( And( And( humanTravel[i] , wolf  ==  wolf[i] )  , goat[i+1]  ==  goat[i] - 1 )  , cabbage  ==  cabbage[i] )  
t1:  And( And( And( humanTravel[i] , wolf  ==  wolf[i] + 1 )  , goat[i+1]  ==  goat[i] )  , cabbage  ==  cabbage[i] )  
t4:  And( And( And( humanTravel[i] , wolf  ==  wolf[i] - 1 )  , goat[i+1]  ==  goat[i] )  , cabbage  ==  cabbage[i] )  
safeAlone:  And( goat[i+1] != wolf , goat[i+1] != cabbage )  
wolfSide:  Or( wolf[i]  ==  0 , wolf[i]  ==  1 )  
humanTravel:  human[i] != human[i+1] 
side:  And( And( And( humanSide[i] , wolfSide[i] )  , goatSide[i] )  , cabbageSide[i] )  
safe:  Or( safeAlone[i] , goat[i+1]  ==  human[i+1] )  
objectTravel:  Or( Or( Or( Or( Or( Or( t1[i] , t2[i] )  , t3[i] )  , t4[i] )  , t5[i] )  , t6[i] )  , t7[i] )  
data:  And( And( And( side[i] , safe[i] )  , humanTravel[i] )  , objectTravel[i] )  

A feltételeket a rendszer rendezi és beilleszti egy szkriptsablonba, amelyet az SMT rendszer Pythonban történő leírására terveztek.

Üres sablon


import json

from z3 import *

s = Solver()

  
  
    
Steps=7
Num= Steps+1

$code$



#template, only start rest
s.add(data + start)

#template
s.add(final)




ind = 0

f = open("/var/www/html/all/bin/python/log.txt", "a")



while s.check() == sat:
  ind = ind +1
  

  print ind
  m = s.model()
  print m

  print "traversing model..." 
  #for d in m.decls():
	#print "%s = %s" % (d.name(), m[d])

  
 
  f.write(str(m))
  f.write("nn")
  exit()
  #s.add(Or(goat[0] != s.model()[data[0]] )) # prevent next model from using the same assignment as a previous model



print "Total solution number: "
print ind  

f.close()
 


A teljes lánc utolsó állapotára az átutalási tranzakció szakaszban megadott szabályok érvényesek.

Ez azt jelenti, hogy a Z3Prover pontosan olyan feltételrendszereket fog keresni, amelyek végső soron lehetővé teszik a pénzeszközök visszavonását a szerződésből.

Ennek eredményeként automatikusan megkapjuk szerződésünk teljesen működőképes SMT modelljét.
Látható, hogy nagyon hasonlít az előző cikkem modelljéhez, amelyet manuálisan állítottam össze.

Kész sablon


import json

from z3 import *

s = Solver()

  
  
    
Steps=7
Num= Steps+1

human = [ Int('human_%i' % (i + 1)) for i in range(Num) ]
wolf = [ Int('wolf_%i' % (i + 1)) for i in range(Num) ]
goat = [ Int('goat_%i' % (i + 1)) for i in range(Num) ]
cabbage = [ Int('cabbage_%i' % (i + 1)) for i in range(Num) ]
nothing= [  And( human[i] == human[i+1], wolf[i] == wolf[i+1], goat[i] == goat[i+1], cabbage[i] == cabbage[i+1] )   for i in range(Num-1) ]


start= [ human[0] == 1, wolf[0] == 0, goat[0] == 1, cabbage[0] == 0 ]

safeAlone= [  And( goat[i+1] != wolf[i+1] , goat[i+1] != cabbage[i+1] )   for i in range(Num-1) ]
safe= [  Or( safeAlone[i] , goat[i+1]  ==  human[i+1] )   for i in range(Num-1) ]
humanTravel= [  human[i] != human[i+1]  for i in range(Num-1) ]
cabbageSide= [  Or( cabbage[i]  ==  0 , cabbage[i]  ==  1 )   for i in range(Num-1) ]
goatSide= [  Or( goat[i]  ==  0 , goat[i]  ==  1 )   for i in range(Num-1) ]
humanSide= [  Or( human[i]  ==  0 , human[i]  ==  1 )   for i in range(Num-1) ]
t7= [  And( And( And( humanTravel[i] , wolf[i+1]  ==  wolf[i] )  , goat[i+1]  ==  goat[i] )  , cabbage[i+1]  ==  cabbage[i] )   for i in range(Num-1) ]
t3= [  And( And( And( humanTravel[i] , wolf[i+1]  ==  wolf[i] )  , goat[i+1]  ==  goat[i] )  , cabbage[i+1]  ==  cabbage[i] + 1 )   for i in range(Num-1) ]
t6= [  And( And( And( humanTravel[i] , wolf[i+1]  ==  wolf[i] )  , goat[i+1]  ==  goat[i] )  , cabbage[i+1]  ==  cabbage[i] - 1 )   for i in range(Num-1) ]
t2= [  And( And( And( humanTravel[i] , wolf[i+1]  ==  wolf[i] )  , goat[i+1]  ==  goat[i] + 1 )  , cabbage[i+1]  ==  cabbage[i] )   for i in range(Num-1) ]
t5= [  And( And( And( humanTravel[i] , wolf[i+1]  ==  wolf[i] )  , goat[i+1]  ==  goat[i] - 1 )  , cabbage[i+1]  ==  cabbage[i] )   for i in range(Num-1) ]
t1= [  And( And( And( humanTravel[i] , wolf[i+1]  ==  wolf[i] + 1 )  , goat[i+1]  ==  goat[i] )  , cabbage[i+1]  ==  cabbage[i] )   for i in range(Num-1) ]
t4= [  And( And( And( humanTravel[i] , wolf[i+1]  ==  wolf[i] - 1 )  , goat[i+1]  ==  goat[i] )  , cabbage[i+1]  ==  cabbage[i] )   for i in range(Num-1) ]
wolfSide= [  Or( wolf[i]  ==  0 , wolf[i]  ==  1 )   for i in range(Num-1) ]
side= [  And( And( And( humanSide[i] , wolfSide[i] )  , goatSide[i] )  , cabbageSide[i] )   for i in range(Num-1) ]
objectTravel= [  Or( Or( Or( Or( Or( Or( t1[i] , t2[i] )  , t3[i] )  , t4[i] )  , t5[i] )  , t6[i] )  , t7[i] )   for i in range(Num-1) ]
data= [ Or(  And( And( And( side[i] , safe[i] )  , humanTravel[i] )  , objectTravel[i] )   , nothing[i]) for i in range(Num-1) ]


fState=  And( And( And( human[Steps]  ==  1 , wolf[Steps]  ==  1 )  , goat[Steps]  ==  1 )  , cabbage[Steps]  ==  1 )  
final=  fState 




#template, only start rest
s.add(data + start)

#template
s.add(final)




ind = 0

f = open("/var/www/html/all/bin/python/log.txt", "a")



while s.check() == sat:
  ind = ind +1
  

  print ind
  m = s.model()
  print m

  print "traversing model..." 
  #for d in m.decls():
	#print "%s = %s" % (d.name(), m[d])

  
 
  f.write(str(m))
  f.write("nn")
  exit()
  #s.add(Or(goat[0] != s.model()[data[0]] )) # prevent next model from using the same assignment as a previous model



print "Total solution number: "
print ind  

f.close()
 


Az indulás után a Z3Prover megoldja az intelligens szerződést, és olyan tranzakciós láncot biztosít számunkra, amely lehetővé teszi a pénzfelvételt:

Winning transaction chain found:
Data transaction: human= 0, wolf= 0, goat= 1, cabbage= 0
Data transaction: human= 1, wolf= 0, goat= 1, cabbage= 1
Data transaction: human= 0, wolf= 0, goat= 0, cabbage= 1
Data transaction: human= 1, wolf= 1, goat= 0, cabbage= 1
Data transaction: human= 0, wolf= 1, goat= 0, cabbage= 1
Data transaction: human= 1, wolf= 1, goat= 1, cabbage= 1
Data transaction: human= 1, wolf= 1, goat= 1, cabbage= 1
Transfer transaction

A kompszerződésen kívül saját szerződésekkel is kísérletezhetsz, vagy kipróbálhatod ezt az egyszerű példát, ami 2 tranzakcióban oldódik meg.

let contract = tx.sender
let a= extract(getInteger(contract,"a"))
let b= extract(getInteger(contract,"b"))
let c= extract(getInteger(contract,"c"))
let d= extract(getInteger(contract,"d"))

match tx {
case t:DataTransaction =>
let na= extract(getInteger(t.data,"a")) 
let nb= extract(getInteger(t.data,"b"))
let nc= extract(getInteger(t.data,"c"))
let nd= extract(getInteger(t.data,"d"))
   
   nd == 0 || a == 100 - 5
case s:TransferTransaction =>
   ( a + b - c ) * d == 12

case _ => true

}

Mivel ez a legelső verzió, a szintaxis nagyon korlátozott, és előfordulhatnak hibák.
A következő cikkekben a virtuális gép továbbfejlesztésére szeretnék kitérni, és bemutatni, hogyan lehet formálisan ellenőrzött okosszerződéseket létrehozni a segítségével, nem csak megoldani.

A karakteres virtuális gép a címen érhető el http://2.59.42.98/hyperbox/
A szimbolikus virtuális gép forráskódjának rendbetétele és az ottani megjegyzések hozzáadása után azt tervezem, hogy felteszem a GitHubra ingyenes hozzáféréshez.

Forrás: will.com

Hozzászólás