github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/script/witness.go (about)

     1  package script
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"encoding/hex"
     7  	"fmt"
     8  
     9  	"github.com/piotrnar/gocoin/lib/btc"
    10  )
    11  
    12  type witness_ctx struct {
    13  	stack scrStack
    14  }
    15  
    16  func (w *witness_ctx) IsNull() bool {
    17  	return w.stack.size() == 0
    18  }
    19  
    20  func (c *SigChecker) ExecuteWitnessScript(stack *scrStack, scriptPubKey []byte, flags uint32, sigversion int, execdata *btc.ScriptExecutionData) bool {
    21  	if sigversion == SIGVERSION_TAPSCRIPT {
    22  		var pc int
    23  		for pc < len(scriptPubKey) {
    24  			opcode, _, le, e := btc.GetOpcode(scriptPubKey[pc:])
    25  			if e != nil {
    26  				// Note how this condition would not be reached if an unknown OP_SUCCESSx was found
    27  				if DBG_ERR {
    28  					fmt.Println("SCRIPT_ERR_BAD_OPCODE")
    29  				}
    30  				return false
    31  			}
    32  			// New opcodes will be listed here. May use a different sigversion to modify existing opcodes.
    33  			if IsOpSuccess(opcode) {
    34  				if (flags & VER_DIS_SUCCESS) != 0 {
    35  					if DBG_ERR {
    36  						fmt.Println("SCRIPT_ERR_DISCOURAGE_OP_SUCCESS")
    37  					}
    38  					return false
    39  				}
    40  				if DBG_ERR {
    41  					fmt.Println("IsOpSuccess - yes")
    42  				}
    43  				return true
    44  			}
    45  			pc += le
    46  		}
    47  		// Tapscript enforces initial stack size limits (altstack is empty here)
    48  		if stack.size() > MAX_STACK_SIZE {
    49  			if DBG_ERR {
    50  				fmt.Println("SCRIPT_ERR_STACK_SIZE")
    51  			}
    52  			return false
    53  		}
    54  	}
    55  
    56  	// Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack
    57  	for i := 0; i < stack.size(); i++ {
    58  		if len(stack.at(i)) > btc.MAX_SCRIPT_ELEMENT_SIZE {
    59  			if DBG_ERR {
    60  				fmt.Println("SCRIPT_ERR_PUSH_SIZE")
    61  			}
    62  			return false
    63  		}
    64  	}
    65  
    66  	// Run the script interpreter.
    67  	//DBG_SCR = true
    68  	if !evalScript(scriptPubKey, stack, c, flags, sigversion, execdata) {
    69  		if DBG_ERR {
    70  			fmt.Println("eval script failed")
    71  		}
    72  		return false
    73  	}
    74  
    75  	// Scripts inside witness implicitly require cleanstack behaviour
    76  	if stack.size() != 1 {
    77  		if DBG_ERR {
    78  			fmt.Println("SCRIPT_ERR_EVAL_FALSE")
    79  		}
    80  		return false
    81  	}
    82  
    83  	if !stack.topBool(-1) {
    84  		if DBG_ERR {
    85  			fmt.Println("SCRIPT_ERR_EVAL_FALSE")
    86  		}
    87  		return false
    88  	}
    89  	return true
    90  }
    91  
    92  func (checker *SigChecker) VerifyWitnessProgram(witness *witness_ctx, witversion int, program []byte, flags uint32, is_p2sh bool) bool {
    93  	var stack scrStack
    94  	var scriptPubKey []byte
    95  	var execdata btc.ScriptExecutionData
    96  
    97  	if DBG_SCR {
    98  		fmt.Println("*****************VerifyWitnessProgram", len(checker.Tx.SegWit), witversion, flags, witness.stack.size(), len(program))
    99  	}
   100  
   101  	if witversion == 0 {
   102  		if len(program) == 32 {
   103  			// Version 0 segregated witness program: SHA256(CScript) inside the program, CScript + inputs in witness
   104  			if witness.stack.size() == 0 {
   105  				if DBG_ERR {
   106  					fmt.Println("SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY")
   107  				}
   108  				return false
   109  			}
   110  			stack.copy_from(&witness.stack)
   111  			scriptPubKey = stack.pop()
   112  			sha := sha256.New()
   113  			sha.Write(scriptPubKey)
   114  			sum := sha.Sum(nil)
   115  			if !bytes.Equal(program, sum) {
   116  				if DBG_ERR {
   117  					fmt.Println("32-SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH")
   118  					fmt.Println(hex.EncodeToString(program))
   119  					fmt.Println(hex.EncodeToString(sum))
   120  					fmt.Println(hex.EncodeToString(scriptPubKey))
   121  				}
   122  				return false
   123  			}
   124  			return checker.ExecuteWitnessScript(&stack, scriptPubKey, flags, SIGVERSION_WITNESS_V0, &execdata)
   125  		} else if len(program) == 20 {
   126  			// Special case for pay-to-pubkeyhash; signature + pubkey in witness
   127  			if witness.stack.size() != 2 {
   128  				if DBG_ERR {
   129  					fmt.Println("20-SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH", checker.Tx.Hash.String())
   130  				}
   131  				return false
   132  			}
   133  
   134  			scriptPubKey = make([]byte, 25)
   135  			scriptPubKey[0] = 0x76
   136  			scriptPubKey[1] = 0xa9
   137  			scriptPubKey[2] = 0x14
   138  			copy(scriptPubKey[3:23], program)
   139  			scriptPubKey[23] = 0x88
   140  			scriptPubKey[24] = 0xac
   141  			stack.copy_from(&witness.stack)
   142  			return checker.ExecuteWitnessScript(&stack, scriptPubKey, flags, SIGVERSION_WITNESS_V0, &execdata)
   143  		} else {
   144  			if DBG_ERR {
   145  				fmt.Println("SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH")
   146  			}
   147  			return false
   148  		}
   149  	} else if witversion == 1 && len(program) == 32 && !is_p2sh {
   150  		// TAPROOT
   151  		if (flags & VER_TAPROOT) == 0 {
   152  			if DBG_SCR {
   153  				fmt.Println("VER_TAPROOT not set - verify script OK", checker.Tx.Hash.String(), checker.Idx)
   154  			}
   155  			return true
   156  		}
   157  		stack.copy_from(&witness.stack)
   158  
   159  		if stack.size() == 0 {
   160  			fmt.Println("SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY")
   161  			return false
   162  		}
   163  
   164  		execdata.M_annex_hash = nil
   165  		if stack.size() >= 2 {
   166  			dat := stack.top(-1)
   167  			if len(dat) > 0 && dat[0] == ANNEX_TAG {
   168  				// Drop annex (this is non-standard; see IsWitnessStandard)
   169  				annex := stack.pop()
   170  				sha := sha256.New()
   171  				btc.WriteVlen(sha, uint64(len(annex)))
   172  				sha.Write(annex)
   173  				execdata.M_annex_hash = sha.Sum(nil)
   174  			}
   175  		}
   176  		// execdata.M_annex_init - it doesn't seem like we need this
   177  
   178  		if stack.size() == 1 {
   179  			// Key path spending (stack size is 1 after removing optional annex)
   180  			return checker.CheckSchnorrSignature(stack.top(-1), program, SIGVERSION_TAPROOT, &execdata)
   181  		} else {
   182  			// Script path spending (stack size is >1 after removing optional annex)
   183  			control := stack.pop()
   184  			script_bytes := stack.pop()
   185  			//exec_script = script_bytes
   186  			if len(control) < TAPROOT_CONTROL_BASE_SIZE || len(control) > TAPROOT_CONTROL_MAX_SIZE || ((len(control)-TAPROOT_CONTROL_BASE_SIZE)%TAPROOT_CONTROL_NODE_SIZE) != 0 {
   187  				if DBG_ERR {
   188  					fmt.Println("SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE")
   189  				}
   190  				return false
   191  			}
   192  
   193  			if !VerifyTaprootCommitment(control, program, script_bytes, &execdata.M_tapleaf_hash) {
   194  				if DBG_ERR {
   195  					fmt.Println("VerifyTaprootCommitment: SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH")
   196  				}
   197  				return false
   198  			}
   199  
   200  			if (control[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT {
   201  				// Tapscript (leaf version 0xc0)
   202  				execdata.M_validation_weight_left = int64(witness.stack.GetSerializeSize() + VALIDATION_WEIGHT_OFFSET)
   203  				execdata.M_validation_weight_left_init = true
   204  				return checker.ExecuteWitnessScript(&stack, script_bytes, flags, SIGVERSION_TAPSCRIPT, &execdata)
   205  			}
   206  			if (flags & VER_DIS_TAPVER) != 0 {
   207  				fmt.Println("SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION")
   208  				return false
   209  			}
   210  			return true
   211  		}
   212  	} else if (flags & VER_WITNESS_PROG) != 0 {
   213  		if DBG_ERR {
   214  			fmt.Println("SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM")
   215  		}
   216  		return false
   217  	} else {
   218  		// Higher version witness scripts return true for future softfork compatibility
   219  		return true
   220  	}
   221  	// There is intentionally no return statement here, to be able to use "control reaches end of non-void function" warnings to detect gaps in the logic above.
   222  }
   223  
   224  func lexicographical_compare(d1, d2 []byte) bool {
   225  	first1, first2 := 0, 0
   226  	last1, last2 := len(d1), len(d2)
   227  	for first1 != last1 {
   228  		if first2 == last2 || d2[first2] < d1[first1] {
   229  			return false
   230  		}
   231  		if d1[first1] < d2[first2] {
   232  			return true
   233  		}
   234  		first1++
   235  		first2++
   236  	}
   237  	return first2 != last2
   238  }
   239  
   240  // (control, program, script_bytes, execdata.M_tapleaf_hash)
   241  func VerifyTaprootCommitment(control, program, script []byte, tapleaf_hash *[]byte) bool {
   242  	path_len := (len(control) - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE
   243  	//! The internal pubkey (x-only, so no Y coordinate parity).
   244  	p := control[1:TAPROOT_CONTROL_BASE_SIZE]
   245  	//! The output pubkey (taken from the scriptPubKey).
   246  	q := program //const XOnlyPubKey q{uint256(program)};
   247  	// Compute the tapleaf hash.
   248  
   249  	sha := btc.Hasher(btc.HASHER_TAPLEAF)
   250  	sha.Write([]byte{control[0] & TAPROOT_LEAF_MASK})
   251  	btc.WriteVlen(sha, uint64(len(script)))
   252  	sha.Write(script)
   253  	*tapleaf_hash = sha.Sum(nil)
   254  
   255  	// Compute the Merkle root from the leaf and the provided path.
   256  	k := *tapleaf_hash
   257  	for i := 0; i < path_len; i++ {
   258  		ss_branch := btc.Hasher(btc.HASHER_TAPBRANCH)
   259  		tmp := TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE*i
   260  		node := control[tmp : tmp+TAPROOT_CONTROL_NODE_SIZE]
   261  
   262  		if lexicographical_compare(k, node) {
   263  			ss_branch.Write(k)
   264  			ss_branch.Write(node)
   265  		} else {
   266  			ss_branch.Write(node)
   267  			ss_branch.Write(k)
   268  		}
   269  		k = ss_branch.Sum(nil)
   270  	}
   271  
   272  	// Compute the tweak from the Merkle root and the internal pubkey.
   273  	sha = btc.Hasher(btc.HASHER_TAPTWEAK)
   274  	sha.Write(p)
   275  	sha.Write(k)
   276  	k = sha.Sum(nil)
   277  	// Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity.
   278  	return btc.CheckPayToContract(q, p, k, (control[0]&1) != 0)
   279  }