C'è un ultimo aspetto degli Script Bitcoin che è cruciale per sbloccare il loro vero potere: i condizionali ti permettono di creare vari percorsi di esecuzione.
Hai già visto un condizionale negli script: OP_VERIFY
(0x69). Estrae l'elemento in cima allo stack e verifica se è vero; in caso contrario termina l'esecuzione dello script.
Verify è solitamente incorporato in altri opcode. Hai già visto OP_EQUALVERIFY
(0xad), OP_CHECKLOCKTIMEVERIFY
(0xb1) e OP_CHECKSEQUENCEVERIFY
(0xb2). Ciascuno di questi opcode esegue la sua azione principale (equal, checklocktime o checksequence) e poi esegue un verify successivamente. Gli altri opcode verify che non hai visto sono: OP_NUMEQUALVERIFY
(0x9d), OP_CHECKSIGVERIFY
(0xad) e OP_CHECKMULTISIGVERIFY
(0xaf).
Quindi come è OP_VERIFY
un condizionale? È il tipo più potente di condizionale. Usando OP_VERIFY
, se una condizione è vera, lo Script continua l'esecuzione, altrimenti lo Script termina. Questo è il modo in cui controlli le condizioni che sono assolutamente necessarie affinché uno Script abbia successo. Ad esempio, lo script P2PKH (OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
) ha due condizioni richieste: (1) che la chiave pubblica fornita corrisponda all'hash della chiave pubblica; e (2) che la firma fornita corrisponda a quella chiave pubblica. Un OP_EQUALVERIFY
è usato per il confronto dell'hash della chiave pubblica perché è una condizione assolutamente necessaria. Non vuoi che lo script continui se ciò fallisce.
Potresti notare che non c'è OP_VERIFY
alla fine di questo (o della maggior parte degli) script, nonostante la condizione finale sia richiesta. Questo perché Bitcoin effettua effettivamente un OP_VERIFY
alla fine di ogni Script, per garantire che il risultato finale dello stack sia vero. Non vuoi fare un OP_VERIFY
prima della fine dello script, perché devi lasciare qualcosa nello stack da verificare!
L'altro principale condizionale in Bitcoin Script è il classico OP_IF
(0x63) / OP_ELSE
(0x67) / OP_ENDIF
(0x68). Questo è il controllo di flusso tipico: se OP_IF
rileva una dichiarazione vera, esegue il blocco sotto di esso; altrimenti, se c'è un OP_ELSE
, esegue quello; e OP_ENDIF
segna la fine del blocco finale.
⚠️ ATTENZIONE: Questi condizionali sono tecnicamente anche opcode, ma come per i numeri piccoli, lasceremo il prefissoOP_
per brevità e chiarezza. Quindi scriveremoIF
,ELSE
eENDIF
invece diOP_IF
,OP_ELSE
eOP_ENDIF
.
Ci sono due grandi problemi nei condizionali. Rendono più difficile leggere e valutare gli script se non stai attento.
Primo, il condizionale IF
controlla la verità di ciò che è prima di esso (cioè ciò che è nello stack), non ciò che è dopo di esso.
Secondo, il condizionale IF
tende a essere nello script di blocco e ciò che sta controllando tende a essere nello script di sblocco.
Certo, potresti dire, è così che funziona Bitcoin Script. I condizionali usano la notazione polacca inversa e adottano il paradigma standard di sblocco/blocco, proprio come tutto il resto nel Bitcoin Scripting. Tutto ciò è vero, ma va anche contro il modo standard in cui leggiamo i condizionali IF/ELSE in altri linguaggi di programmazione; quindi, è facile leggere inconsciamente i condizionali Bitcoin in modo errato.
Considera il seguente codice: IF OP_DUP OP_HASH160 <pubKeyHashA> ELSE OP_DUP OP_HASH160 <pubKeyHashB> ENDIF OP_EQUALVERIFY OP_CHECKSIG
.
Guardando i condizionali in notazione prefissa potresti leggere questo come::
IF (OP_DUP) THEN
OP_HASH160
OP_PUSHDATA <pubKeyHashA>
ELSE
OP_DUP
OP_HASH160
OP_PUSHDATA <pubKeyHashB>
ENDIF
OP_EQUALVERIFY
OP_CHECKSIG
Quindi, potresti pensare, se OP_DUP
ha successo, allora eseguiamo il primo blocco, altrimenti il secondo. Ma ciò non ha senso! Perché OP_DUP
non dovrebbe avere successo?!
E, infatti, non ha senso, perché abbiamo letto accidentalmente l'affermazione usando la notazione sbagliata. La lettura corretta di questo è:
IF
OP_DUP
OP_HASH160
OP_PUSHDATA <pubKeyHashA>
ELSE
OP_DUP
OP_HASH160
OP_PUSHDATA <pubKeyHashB>
ENDIF
OP_EQUALVERIFY
OP_CHECKSIG
L'affermazione che sarà valutata come True
o False
è posta nello stack prima di eseguire l'IF
, quindi il blocco di codice corretto viene eseguito in base a quel risultato.
Questo esempio di codice è inteso come una multisignature 1-di-2 di basso livello. Il proprietario di <privKeyA>
metterebbe <signatureA> <pubKeyA> True
nel suo script di sblocco, mentre il proprietario di <privKeyB>
metterebbe <signatureB> <pubKeyB> False
nel suo script di sblocco. Quel True
o False
finale è ciò che viene controllato dall'istruzione IF
/ELSE
. Dice allo script quale hash della chiave pubblica controllare, quindi OP_EQUALVERIFY
e OP_CHECKSIG
alla fine fanno il lavoro reale.
Con una comprensione di base dei condizionali Bitcoin, siamo ora pronti a eseguire uno script. Creeremo una leggera variante della nostra multisignature 1-di-2 di basso livello dove i nostri utenti non devono ricordare se sono True
o False
. Invece, se necessario, lo script controlla entrambi gli hash delle chiavi pubbliche, richiedendo solo un successo:
OP_DUP OP_HASH160 <pubKeyHashA> OP_EQUAL
IF
OP_CHECKSIG
ELSE
OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG
ENDIF
Ricorda la notazione polacca inversa! Quell'istruzione IF
si riferisce all'OP_EQUAL
prima di essa, non all'OP_CHECKSIG
dopo di essa!
Ecco come funziona effettivamente se sbloccato con <signatureA> <pubKeyA>
:
Script: <signatureA> <pubKeyA> OP_DUP OP_HASH160 <pubKeyHashA> OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Stack: [ ]
Per prima cosa, mettiamo le costanti nello stack:
Script: OP_DUP OP_HASH160 <pubKeyHashA> OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Stack: [ <signatureA> <pubKeyA> ]
Poi eseguiamo i primi comandi ovvi, OP_DUP
e OP_HASH160
, e mettiamo un'altra costante:
Script: OP_HASH160 <pubKeyHashA> OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Running: <pubKeyA> OP_DUP
Stack: [ <signatureA> <pubKeyA> <pubKeyA> ]
Script: <pubKeyHashA> OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Running: <pubKeyA> OP_HASH160
Stack: [ <signatureA> <pubKeyA> <pubKeyHashA> ]
Script: OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Stack: [ <signatureA> <pubKeyA> <pubKeyHashA> <pubKeyHashA> ]
Successivamente, eseguiamo l'OP_EQUAL
, che è ciò che alimenterà l'IF
:
Script: IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Running: <pubKeyHashA> <pubKeyHashA> OP_EQUAL
Stack: [ <signatureA> <pubKeyA> True ]
Ora l'IF
viene eseguito e, poiché c'è un True
, esegue solo il primo blocco, eliminando tutto il resto:
Script: OP_CHECKSIG
Running: True IF
Stack: [ <signatureA> <pubKeyA> ]
E l'OP_CHECKSIG
risulterà True
:
Script:
Running: <signatureA> <pubKeyA> OP_CHECKSIG
Stack: [ True ]
Ecco come funziona effettivamente se sbloccato con <signatureB> <pubKeyB>
:
Script: <signatureB> <pubKeyB> OP_DUP OP_HASH160 <pubKeyHashA> OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Stack: [ ]
Per prima cosa, mettiamo le costanti nello stack:
Script: OP_DUP OP_HASH160 <pubKeyHashA> OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Stack: [ <signatureB> <pubKeyB> ]
Poi eseguiamo i primi comandi ovvi, OP_DUP
e OP_HASH160
, e mettiamo un'altra costante:
Script: OP_HASH160 <pubKeyHashA> OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Running: <pubKeyB> OP_DUP
Stack: [ <signatureB> <pubKeyB> <pubKeyB> ]
Script: <pubKeyHashA> OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Running: <pubKeyB> OP_HASH160
Stack: [ <signatureB> <pubKeyB> <pubKeyHashB> ]
Script: OP_EQUAL IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Stack: [ <signatureB> <pubKeyB> <pubKeyHashB> <pubKeyHashA> ]
Successivamente, eseguiamo l'OP_EQUAL
, che è ciò che alimenterà l'IF
:
Script: IF OP_CHECKSIG ELSE OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG ENDIF
Running: <pubKeyHashB> <pubKeyHashA> OP_EQUAL
Stack: [ <signatureB> <pubKeyB> False ]
Ops! Il risultato è stato False
perché <pubKeyHashB>
non è uguale a <pubKeyHashA>
. Ora quando l'IF
viene eseguito, si riduce solo all'istruzione ELSE
:
Script: OP_DUP OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG
Running: False IF
Stack: [ <signatureB> <pubKeyB> ]
Successivamente, rifacciamo tutto il processo, iniziando con un altro OP_DUP
, ma alla fine testando contro l'altro pubKeyHash
:
Script: OP_HASH160 <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG
Running: <pubKeyB> OP_DUP
Stack: [ <signatureB> <pubKeyB> <pubKeyB> ]
Script: <pubKeyHashB> OP_EQUALVERIFY OP_CHECKSIG
Running: <pubKeyB> OP_HASH160
Stack: [ <signatureB> <pubKeyB> <pubKeyHashB> ]
Script: OP_EQUALVERIFY OP_CHECKSIG
Stack: [ <signatureB> <pubKeyB> <pubKeyHashB> <pubKeyHashB> ]
Script:OP_CHECKSIG
Running: <pubKeyHashB> <pubKeyHashB> OP_EQUALVERIFY
Stack: [ <signatureB> <pubKeyB> ]
Script:
Running: <signatureB> <pubKeyB> OP_CHECKSIG
Stack: [ True ]
TQuesto probabilmente non è altrettanto efficiente come una vera multisignature di Bitcoin, ma è un buon esempio di come i risultati inseriti nello stack dai test precedenti possono essere utilizzati per alimentare i condizionali futuri. In questo caso, è il fallimento della prima firma che dice al condizionale che dovrebbe controllare la seconda.
Ci sono alcuni altri condizionali degni di nota. Il più importante è OP_NOTIF
(0x64), che è l'opposto di OP_IF
: esegue il blocco successivo se l'elemento in cima è False
. Un ELSE
può essere inserito con esso, che come al solito viene eseguito se il primo blocco non viene eseguito. Si termina ancora con OP_ENDIF
.
C'è anche un OP_IFDUP
(0x73), che duplica l'elemento in cima allo stack solo se non è 0.
Queste opzioni sono usate molto meno spesso della costruzione principale IF
/ELSE
/ENDIF
.
I condizionali in Bitcoin Script ti permettono di fermare lo script (usando OP_VERIFY
) o di scegliere diversi rami di esecuzione (usando OP_IF
). Tuttavia, leggere OP_IF
può essere un po' complicato. Ricorda che è l'elemento inserito nello stack prima che l'OP_IF
venga eseguito a controllarne l'esecuzione; quell'elemento sarà tipicamente parte dello script di sblocco (o altrimenti un risultato diretto degli elementi nello script di sblocco).
🔥 Qual è il potere dei condizionali? I Condizionali degli Script sono l'ultimo grande blocco costitutivo negli Script Bitcoin. Sono ciò che è necessario per trasformare semplici Script Bitcoin statici in Script Bitcoin complessi e dinamici che possono essere valutati in modo diverso in base a tempi diversi, circostanze diverse o input diversi degli utenti. In altre parole, sono la base finale dei contratti intelligenti.
Continua a "Espandere gli Script Bitcoin" col Capitolo 12.2Usare Altri Comandi di Scripting.