Bitcoin MULTISIG


In reality, some organization may require more than 1 director's cheque signature in order to release the fund. Same thing accomplish this in bitcoin is using MULTISIG mechanism.
P2SH.Multisig (Multisig wrapped in P2SH)
  • Most common way to use multisig.
  • Described in BIP67.
  • Exist after P2MS.
  • Use of P2SH address implies that spender no longer bear for high tx fees as compare to P2MS below.
P2MS
  • Stands for "Pay To Multi Signature"
  • M-of-N Standard Transactions described in BIP11.
  • Not common as P2MS has no address format and limited to 3 public keys.
  • In this tutorial, you are able to fund into P2MS's ScriptPubKey for better understanding to bitcoin script.
Script Pair of P2SH.Multisig
ScriptPubKey: OP_HASH160 <redeemScriptHash> OP_EQUAL
ScriptSig: OP_0 < ... > OP_PUSHDATA1 <redeemScript>
Redeem Script : < ... > OP_CHECKMULTISIG
Script Pair of P2MS
ScriptSig:
ScriptPubKey :

Multisig Address

<?php 
use BitWasp\Bitcoin\Bitcoin;
use BitWasp\Bitcoin\Network\NetworkFactory;
use BitWasp\Buffertools\Buffer;
use BitWasp\Bitcoin\Key\Factory\PublicKeyFactory;
use BitWasp\Bitcoin\Script\ScriptFactory;
use BitWasp\Bitcoin\Script\P2shScript;
use BitWasp\Bitcoin\Script\Opcodes;

include_once "../libraries/vendor/autoload.php";

$M_N_Range = range(1,15);

include_once("html_iframe_header.php");

if ($_SERVER['REQUEST_METHOD'] == 'POST') {

	try {
		
		$networkClass   = $_POST['network'];
		Bitcoin::setNetwork(NetworkFactory::$networkClass());
		$network        = Bitcoin::getNetwork();
		$pubKeys = [];
		
		$publicKeyFactory = new PublicKeyFactory();
		if (!in_array($_POST['reqsig'], $M_N_Range)) {
			throw new Exception("'Required Signature' value is not valid.");
		} else {
			foreach($_POST['pubkey'] as $pubkey_hex) {
				if (strlen($pubkey_hex) > 0 AND !ctype_xdigit($pubkey_hex)) {
					throw new Exception("Public key must be hex.");
				}
				
				if (strlen($pubkey_hex) > 0 AND ctype_xdigit($pubkey_hex)) {
					$validPubKeys[] = $publicKeyFactory->fromHex($pubkey_hex);;
				}
			}
		}
		
		if ($_POST['reqsig'] > count($validPubKeys)) {
			throw new Exception("Required signature value should not exceed number of public key.");
		}  
		
		// make a n-of-m multisignature script
		$multisig = ScriptFactory::scriptPubKey()->multisig($_POST['reqsig'], $validPubKeys, $sort = false);
		
		// use the P2shScript 'decorator' to 'extend' script with extra functions relevant to a P2SH script
		$redeemScript = new P2shScript($multisig);
		
		$scriptPubKey = $redeemScript->getOutputScript();
		
		$opcodes = $scriptPubKey->getOpcodes();
	
	?>
		<div class="table-responsive">
			<h6 class="mt-3">P2SH.Multisig</h6>
			<table border=0 class='table'>
				<tr style='background-color:#f0f0f0'><td>Base58 address</td><td><?php echo $redeemScript->getAddress()->getAddress();?></td></tr>
				<tr><td>Redeem Script Hex </td><td><?php echo $redeemScript->getHex();?></td></tr>
				<tr><td>Redeem Script Asm</td>
					<td>
						<?php 
						foreach( $redeemScript->getScriptParser()->decode() as $operation ) {
							if ($operation->isPush()) {
								echo htmlentities("<{$operation->getData()->getHex()}> ");
							} else {
								echo $opcodes->getOp($operation->getOp()) . " " ;
							}
						}
						?>
					</td>
				</tr>
				
				<tr><td>Redeem Script Hash Hex</td><td><?php echo $redeemScript->getScriptHash()->getHex();?></td></tr>
				
				<tr style='background-color:#f0f0f0'><td>ScriptPubKey Hex </td><td><?php echo $scriptPubKey->getHex()?></td></tr>
				<tr style='background-color:#f0f0f0'><td>ScriptPubKey Asm</td>
					<td>
						<?php 
						foreach( $scriptPubKey->getScriptParser()->decode() as $operation ) {
							if ($operation->isPush()) {								
								echo htmlentities("<{$operation->getData()->getHex()}> ");
							} else {
								echo $opcodes->getOp($operation->getOp()) . " " ;
							}
						}
						?>
					</td>
				</tr>
			</table>
			
			<?php
			if (@count($validPubKeys) <= 3) {
			?>
				<h6 class="mt-3">P2MS</h6>
				<table border=0 class='table'>
					<tr><td>ScriptPubKey Hex </td><td><?php echo $redeemScript->getHex();?></td></tr>
					
					<tr><td>ScriptPubKey Asm</td>
						<td>
							<?php 
							foreach( $redeemScript->getScriptParser()->decode() as $operation ) {
								if ($operation->isPush()) {
									
									echo htmlentities("<{$operation->getData()->getHex()}> ");
								} else {
									echo $opcodes->getOp($operation->getOp()) . " " ;
								}
							}
							?>
						</td>
					</tr>
				</table>
			<?php
			}
			?>
		</div>
<?php 
	} catch (Exception $e) {
		$errmsg .= "Problem found. " . $e->getMessage();

	}
} 

if ($errmsg) {
?>
	<div class="alert alert-danger">
		<strong>Error!</strong> <?php echo $errmsg?>
	</div>
<?php
}
?>
<form action='' method='post'>
	<div class="form-group">
		<label for="network">Network:</label>
		<select id="network" name="network" class="form-control" >
			<?php
			$networks = get_class_methods(new NetworkFactory());
			foreach($networks as $network) {
				echo "<option value='{$network}'".($network == $_POST['network'] ? " selected": "").">{$network}</option>";
			}
			?>
		</select>
	</div>
	
	<div class="form-group">
		<label for="pubkey">Public Key Hex:</label>
		
		<div class="input-group mb-3">
			<input class="form-control" type='text' name='pubkey[]' id='pubkey' value='<?php echo $_POST['pubkey'][0]?>'>
			<div class="input-group-append">
				<input class="btn btn-success" type="button" value="+" onclick="$('#multisig-pubkey-holder').find('div').first().clone().appendTo('#multisig-pubkey-holder')"/>
			</div>
		</div>
	</div>
	<?php
	$displayPublickey = count($_POST['pubkey'])-1;
	$displayPublickey = max(1, $displayPublickey);
	?>
	<div id="multisig-pubkey-holder" class="form-group">
		
		<?php
		foreach(range(1, $displayPublickey) as $n) {
		?>
		<div class="input-group mb-3">
			<input class="form-control" type='text' name='pubkey[]' value='<?php echo $_POST['pubkey'][$n]?>'>
			<div class="input-group-append">
				<input class="btn btn-success" type="button" value=" - " onclick="
					var length = $(this).closest('#multisig-pubkey-holder').find('input[value=\' - \']').length;
					if (length > 1) {
						$(this).parent('div').parent('div').remove();
					} else {
						alert('At least 2 public keys are required');
					}
				"/>
			</div>
		</div>
		<?Php
		}
		?>
		
	</div>
	
	<div class="form-group">
		<label for="reqsig">Required Signature To Spend:</label>
		<select id="reqsig" name="reqsig" class="form-control" >
			<?php
			foreach($M_N_Range as $k) {
				echo "<option value='{$k}'".($k == $_POST['reqsig'] ? " selected": "").">{$k}</option>";
			}
			?>
		</select>
	</div>
	
	
	<input type='submit' class="btn btn-success btn-block"/>
</form>
<?php
include_once("html_iframe_footer.php");		

Fund & Spend Multisig

<?php 
use BitWasp\Bitcoin\Bitcoin;
use BitWasp\Buffertools\Buffer;
use BitWasp\Bitcoin\Network\NetworkFactory;
use BitWasp\Bitcoin\Key\Factory\PrivateKeyFactory;
use BitWasp\Bitcoin\Address\AddressCreator;
use BitWasp\Bitcoin\Transaction\Factory\TxBuilder;
use BitWasp\Bitcoin\Transaction\TransactionFactory;
use BitWasp\Bitcoin\Transaction\TransactionOutput;
use BitWasp\Bitcoin\Transaction\Factory\Signer;
use BitWasp\Bitcoin\Script\ScriptType;
use BitWasp\Bitcoin\Script\ScriptFactory;
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;

include_once "../libraries/vendor/autoload.php";

$noOfInputs = 10;
$noOfOutputs = 1;

include_once("html_iframe_header.php");

if ($_GET['tab'] == 'form2_tabitem1' AND $_SERVER['REQUEST_METHOD'] == 'POST') {
	try {
		$networkClass   = $_POST['network'];
		
		Bitcoin::setNetwork(NetworkFactory::$networkClass());
		
		$network        = Bitcoin::getNetwork();
		$ecAdapter      = Bitcoin::getEcAdapter();
		$privKeyFactory = new PrivateKeyFactory();
		
		$addrCreator = new AddressCreator();
		
		$spendTx = TransactionFactory::build();
		
		$signItems = [];
		
		
		if (!is_numeric($_POST['no_of_inputs']) OR !is_numeric($_POST['no_of_outputs'])) {
			throw new Exception("Error in 'no_of_inputs' or 'no_of_outputs'.");
		}
			
		foreach(range(1,$_POST['no_of_inputs']) as $thisInput) {
			$utxoHash = trim($_POST["utxo_hash_{$thisInput}"]);
			$utxoNOutput = trim($_POST["utxo_n_{$thisInput}"]);
			$privkeyhex = trim($_POST["privkey_{$thisInput}"]);
			$utxoScript = trim($_POST["utxo_script_{$thisInput}"]);
			
			if (strlen($utxoHash)>0 AND strlen($utxoNOutput) > 0 AND strlen($privkeyhex) > 0) {
				$spendTx = $spendTx->input($utxoHash, $utxoNOutput);
				$signItems[] = [$privkeyhex, $utxoScript];
			} else {
				throw new Exception("Error in 'input#{$thisInput}'.");
			}
		}
		
		foreach(range(1,$_POST['no_of_outputs']) as $thisOutput) {
			
			$address = trim($_POST["address_{$thisOutput}"]);
			$amount = trim($_POST["amount_{$thisOutput}"]);
			
			if (!strlen($address) or !strlen($amount)) {
				throw new Exception("Error in 'output#{$thisOutput}'.");
			}
			
			if (ctype_xdigit($address)) {//hex
				
				$recipient = ScriptFactory::fromHex($address);
				
				$decodeScript = (new OutputClassifier())->decode($recipient);
				
				if ($decodeScript->getType() != ScriptType::MULTISIG) {
					throw new Exception("Invalid P2MS output in 'output#{$thisOutput}' (Check scriptPubKey).");
				}
				
				$spendTx = $spendTx->output($amount, $recipient);
				
				
			} else {
				
				
				$recipient = $addrCreator->fromString($address);
				
				$decodeScript = (new OutputClassifier())->decode($recipient->getScriptPubKey());
				
				if ($decodeScript->getType() != ScriptType::P2SH) {
					throw new Exception("Invalid P2SH addresss in 'output#{$thisOutput}' (Check scriptPubKey).");
				}
				
				$spendTx = $spendTx->payToAddress($amount, $recipient);
			}
		}
		
		$thisTx = $spendTx->get();
		
		$signer = new Signer($thisTx, $ecAdapter);
		
		foreach($signItems as $nIn=>$signItem) {
			$privateKey = $privKeyFactory->fromHexCompressed($signItem[0]);
			
			$scriptPubKey = ScriptFactory::fromHex($signItem[1]);
			
			$txOutput = new TransactionOutput(0, $scriptPubKey );
			
			$signer = $signer->sign($nIn, $privateKey, $txOutput);
		}
		
		
		
	?>
		<div class="alert alert-success">
			<h6 class="mt-3">Final TX Hex</h6>
			
			<textarea class="form-control" rows="5" id="comment" readonly><?php echo $signer->get()->getHex();?></textarea>
		</div>
	<?php
	
	} catch (Exception $e) {
		$errmsg .= "Problem found. " . $e->getMessage();

	}
}

if ($errmsg) {
?>
	<div class="alert alert-danger">
		<strong>Error!</strong> <?php echo $errmsg?>
	</div>
<?php
}
?>
<form action='?tab=form2_tabitem1#hashtag2' method='post'>
	<div class="form-group">
		<label for="network">Network: </label>
		<select name="network" id="network" class="form-control">
			<?php
			$networks = get_class_methods(new NetworkFactory());
			foreach($networks as $network) {
				echo "<option value='{$network}'".($network == $_POST['network'] ? " selected": "").">{$network}</option>";
			}
			?>
		</select>
	</div>
	
	<div class="row">
		<div class="col-sm-6">
			
			<div class="form-row">
				<div class="form-group col">
					<label for="no_of_inputs">Inputs: </label> 
					<select class="form-control" style="width:auto;" id="no_of_inputs" name='no_of_inputs' onchange="
					var self = $(this);
					var thisvalue = self.val();
					var form = self.closest('form');
					$('div[id^=row_input_]',form).hide();
					for(var i=1; i<= thisvalue; i++) { 
						$('div[id=row_input_'+  i + ']',form).show();
					}
					">
						<?php
						foreach(range(1,$noOfInputs) as $thisInput) {
							echo "<option value='{$thisInput}'".($thisInput == $_POST['no_of_inputs'] ? " selected": "").">{$thisInput}</option>";
						}
						?>
					</select>
				</div>
			</div>
			
			<?php
			$selected_n_inputs = is_numeric($_POST['no_of_inputs']) ? $_POST['no_of_inputs'] : 1;
			
			foreach(range(1,$noOfInputs) as $thisInput) {
			?>
			
				<div class="form-row" id='row_input_<?php echo $thisInput?>' style="<?php echo ($thisInput > $selected_n_inputs) ? "display:none" : "display:;"?>">
				
					<div class="form-group  col-sm-1">
						#<?php echo $thisInput?> 
					</div>
					<div class="form-group  col-sm-3">
						
						<input class="form-control" title="UTXO Tx Hash" placeholder='UTXO Tx Hash' type='text' name='utxo_hash_<?php echo $thisInput?>' value='<?php echo $_POST["utxo_hash_{$thisInput}"]?>'>
					</div>
					<div class="form-group  col-sm-1">
						<input class="form-control" title="UTXO N Output" placeholder='N' type='text' name='utxo_n_<?php echo $thisInput?>' value='<?php echo $_POST["utxo_n_{$thisInput}"]?>'>
					</div>
					
					<div class="form-group  col-sm-3">
						<input class="form-control" title="UTXO ScriptPubKey" placeholder='UTXO ScriptPubKey' type='text' name='utxo_script_<?php echo $thisInput?>' value='<?php echo $_POST["utxo_script_{$thisInput}"]?>'>
					</div>
					<div class="form-group  col-sm-4">
						<input class="form-control" title="Private Key Hex, for signing purpose." placeholder='Private Key Hex' type='text' name='privkey_<?php echo $thisInput?>' value='<?php echo $_GET['tab'] == 'form2_tabitem1' ?$_POST["privkey_{$thisInput}"] : ''?>'>
					</div>
				</div>
			<?php
			}
			?>
		</div>
		<div class="col-sm-6">
			<div class="form-row">
				<div class="form-group col">
					<label for="no_of_outputs">Outputs:</label> <select class="form-control" id="no_of_outputs" name='no_of_outputs' style='width:auto;' onchange="
					var self = $(this);
					var thisvalue = self.val();
					var form = self.closest('form');
					$('div[id^=row_output_]',form).hide();
					for(var i=1; i<= thisvalue; i++) { 
						$('div[id=row_output_'+  i + ']',form).show();
					}
					">
						<?php
						foreach(range(1,$noOfOutputs) as $thisOutput) {
							echo "<option value='{$thisOutput}'".($thisOutput == $_POST['no_of_outputs'] ? " selected": "").">{$thisOutput}</option>";
						}
						?>
					</select>
				</div>
			</div>
			<?php
			$selected_n_outputs = is_numeric($_POST['no_of_outputs']) ? $_POST['no_of_outputs'] : 1;
			
			
			foreach(range(1,$noOfOutputs) as $thisOutput) {
			?>
				<div class="form-row" id='row_output_<?php echo $thisOutput?>' style="<?php echo ($thisOutput > $selected_n_outputs) ? "display:none" : "display:;"?>">
					<div class="form-group col-sm-1">
						#<?php echo $thisOutput?> 
					</div>
					
					<div class="form-group col-sm-6">
						<input class="form-control" title='P2SH.Multisig Address / P2MS ScriptPubKey' placeholder='P2SH.Multisig Address / P2MS ScriptPubKey' type='text' name='address_<?php echo $thisOutput?>' value='<?php echo $_POST["address_{$thisOutput}"]?>'>
					</div>
					<div class="form-group col-sm-5">
						<input class="form-control" placeholder='Amount' type='text' name='amount_<?php echo $thisOutput?>' value='<?php echo $_POST["amount_{$thisOutput}"]?>'>
					</div>
				</div>
	<?php
			}
	?>
		</div>
	</div>
	<input type='submit' class="btn btn-success btn-block"/>
</form>
<?php
include_once("html_iframe_footer.php");		
<?php 
use BitWasp\Bitcoin\Bitcoin;
use BitWasp\Bitcoin\Network\NetworkFactory;
use BitWasp\Buffertools\Buffer;
use BitWasp\Bitcoin\Key\Factory\PublicKeyFactory;
use BitWasp\Bitcoin\Script\ScriptFactory;
use BitWasp\Bitcoin\Script\P2shScript;
use BitWasp\Bitcoin\Script\Opcodes;

include_once "../libraries/vendor/autoload.php";

$M_N_Range = range(1,15);

include_once("html_iframe_header.php");

if ($_SERVER['REQUEST_METHOD'] == 'POST') {

	try {
		
		$networkClass   = $_POST['network'];
		Bitcoin::setNetwork(NetworkFactory::$networkClass());
		$network        = Bitcoin::getNetwork();
		$pubKeys = [];
		
		$publicKeyFactory = new PublicKeyFactory();
		if (!in_array($_POST['reqsig'], $M_N_Range)) {
			throw new Exception("'Required Signature' value is not valid.");
		} else {
			foreach($_POST['pubkey'] as $pubkey_hex) {
				if (strlen($pubkey_hex) > 0 AND !ctype_xdigit($pubkey_hex)) {
					throw new Exception("Public key must be hex.");
				}
				
				if (strlen($pubkey_hex) > 0 AND ctype_xdigit($pubkey_hex)) {
					$validPubKeys[] = $publicKeyFactory->fromHex($pubkey_hex);;
				}
			}
		}
		
		if ($_POST['reqsig'] > count($validPubKeys)) {
			throw new Exception("Required signature value should not exceed number of public key.");
		}  
		
		// make a n-of-m multisignature script
		$multisig = ScriptFactory::scriptPubKey()->multisig($_POST['reqsig'], $validPubKeys, $sort = false);
		
		// use the P2shScript 'decorator' to 'extend' script with extra functions relevant to a P2SH script
		$redeemScript = new P2shScript($multisig);
		
		$scriptPubKey = $redeemScript->getOutputScript();
		
		$opcodes = $scriptPubKey->getOpcodes();
	
	?>
		<div class="table-responsive">
			<h6 class="mt-3">P2SH.Multisig</h6>
			<table border=0 class='table'>
				<tr style='background-color:#f0f0f0'><td>Base58 address</td><td><?php echo $redeemScript->getAddress()->getAddress();?></td></tr>
				<tr><td>Redeem Script Hex </td><td><?php echo $redeemScript->getHex();?></td></tr>
				<tr><td>Redeem Script Asm</td>
					<td>
						<?php 
						foreach( $redeemScript->getScriptParser()->decode() as $operation ) {
							if ($operation->isPush()) {
								echo htmlentities("<{$operation->getData()->getHex()}> ");
							} else {
								echo $opcodes->getOp($operation->getOp()) . " " ;
							}
						}
						?>
					</td>
				</tr>
				
				<tr><td>Redeem Script Hash Hex</td><td><?php echo $redeemScript->getScriptHash()->getHex();?></td></tr>
				
				<tr style='background-color:#f0f0f0'><td>ScriptPubKey Hex </td><td><?php echo $scriptPubKey->getHex()?></td></tr>
				<tr style='background-color:#f0f0f0'><td>ScriptPubKey Asm</td>
					<td>
						<?php 
						foreach( $scriptPubKey->getScriptParser()->decode() as $operation ) {
							if ($operation->isPush()) {								
								echo htmlentities("<{$operation->getData()->getHex()}> ");
							} else {
								echo $opcodes->getOp($operation->getOp()) . " " ;
							}
						}
						?>
					</td>
				</tr>
			</table>
			
			<?php
			if (@count($validPubKeys) <= 3) {
			?>
				<h6 class="mt-3">P2MS</h6>
				<table border=0 class='table'>
					<tr><td>ScriptPubKey Hex </td><td><?php echo $redeemScript->getHex();?></td></tr>
					
					<tr><td>ScriptPubKey Asm</td>
						<td>
							<?php 
							foreach( $redeemScript->getScriptParser()->decode() as $operation ) {
								if ($operation->isPush()) {
									
									echo htmlentities("<{$operation->getData()->getHex()}> ");
								} else {
									echo $opcodes->getOp($operation->getOp()) . " " ;
								}
							}
							?>
						</td>
					</tr>
				</table>
			<?php
			}
			?>
		</div>
<?php 
	} catch (Exception $e) {
		$errmsg .= "Problem found. " . $e->getMessage();

	}
} 

if ($errmsg) {
?>
	<div class="alert alert-danger">
		<strong>Error!</strong> <?php echo $errmsg?>
	</div>
<?php
}
?>
<form action='' method='post'>
	<div class="form-group">
		<label for="network">Network:</label>
		<select id="network" name="network" class="form-control" >
			<?php
			$networks = get_class_methods(new NetworkFactory());
			foreach($networks as $network) {
				echo "<option value='{$network}'".($network == $_POST['network'] ? " selected": "").">{$network}</option>";
			}
			?>
		</select>
	</div>
	
	<div class="form-group">
		<label for="pubkey">Public Key Hex:</label>
		
		<div class="input-group mb-3">
			<input class="form-control" type='text' name='pubkey[]' id='pubkey' value='<?php echo $_POST['pubkey'][0]?>'>
			<div class="input-group-append">
				<input class="btn btn-success" type="button" value="+" onclick="$('#multisig-pubkey-holder').find('div').first().clone().appendTo('#multisig-pubkey-holder')"/>
			</div>
		</div>
	</div>
	<?php
	$displayPublickey = count($_POST['pubkey'])-1;
	$displayPublickey = max(1, $displayPublickey);
	?>
	<div id="multisig-pubkey-holder" class="form-group">
		
		<?php
		foreach(range(1, $displayPublickey) as $n) {
		?>
		<div class="input-group mb-3">
			<input class="form-control" type='text' name='pubkey[]' value='<?php echo $_POST['pubkey'][$n]?>'>
			<div class="input-group-append">
				<input class="btn btn-success" type="button" value=" - " onclick="
					var length = $(this).closest('#multisig-pubkey-holder').find('input[value=\' - \']').length;
					if (length > 1) {
						$(this).parent('div').parent('div').remove();
					} else {
						alert('At least 2 public keys are required');
					}
				"/>
			</div>
		</div>
		<?Php
		}
		?>
		
	</div>
	
	<div class="form-group">
		<label for="reqsig">Required Signature To Spend:</label>
		<select id="reqsig" name="reqsig" class="form-control" >
			<?php
			foreach($M_N_Range as $k) {
				echo "<option value='{$k}'".($k == $_POST['reqsig'] ? " selected": "").">{$k}</option>";
			}
			?>
		</select>
	</div>
	
	
	<input type='submit' class="btn btn-success btn-block"/>
</form>
<?php
include_once("html_iframe_footer.php");		








Tutorials
About Us
Contents have been open source in GITHUB. Please give me a ⭐ if you found this helpful :)
Community
Problem? Raise me a new issue.
Support Us
Buy me a coffee. so i can spend more nights for this :)

BTCSCHOOLS would like to present you with more pratical but little theory throughout our tutorials. Pages' content are constantly keep reviewed to avoid mistakes, but we cannot warrant correctness of all contents. While using this site, you agree to accept our terms of use, cookie & privacy policy. Copyright 2019 by BTCSCHOOLS. All Rights Reserved.