Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mandatory Multisig #30

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 108 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<ul class="dropdown-menu">
<li><a href="#newAddress" data-toggle="tab">New Address</a></li>
<li><a href="#newMultiSig" data-toggle="tab">MultiSig Address</a></li>
<li><a href="#newMandatoryMultiSig" data-toggle="tab">Mandatory MultiSig Address</a></li>
<li><a href="#newHDaddress" data-toggle="tab">HD Address</a></li>
<li class="divider"></li>
<li><a href="#newTransaction" data-toggle="tab">Transaction</a></li>
Expand Down Expand Up @@ -377,6 +378,105 @@ <h2>New Multisig Address <small>Secure multisig address</small></h2>
<br>
</div>

<div class="tab-pane tab-content" id="newMandatoryMultiSig">
<h2>New Mandatory Multisig Address <small>Secure multisig address with some mandatory keys</small></h2>

<!-- Not sure how to explain this P2SH
<div class="row">

<div class="col-md-11">
<p>Public keys can be <a href="#newAddress">generated in your browser</a> or from your bitcoin client</a>.</p>
<p>Enter the public keys of all the participants, to create a <a href="https://en.bitcoin.it/wiki/Address#Multi-signature_addresses" target="_blank">multi signature address</a>. Maximum of 15 allowed. Compressed and uncompressed public keys are accepted.</p>
</div>

<div class="col-md-1">
</div>
</div>
-->

<div class="row">
<div class="col-md-11">
<p>Please select the public keys <strong>absolutely necessary</strong> to release the coins. Without the signature of all of the following keys, the coins cannot be released</p>
</div>
</div>

<div id="m_multisigMandatoryPubKeys" class="row">
<div class="form-horizontal">
<div class="col-xs-11">
<input type="text" class="form-control pubkey">
</div>
<a href="javascript:;" class="pubkeyAdd"><span class="glyphicon glyphicon-plus"></span></a>
<br><br>
</div>
</div>

<div class="row">
<div class="col-md-11">
<p>Please select the public keys for the multisignature.</p>
</div>
</div>

<div id="m_multisigPubKeys" class="row">
<div class="form-horizontal">
<div class="col-xs-11">
<input type="text" class="form-control pubkey">
</div>
<a href="javascript:;" class="pubkeyAdd"><span class="glyphicon glyphicon-plus"></span></a>
<br><br>
</div>
</div>

<p>Enter the amount of signatures required to release the coins</p>
<div class="row">
<div class="col-xs-3">
<select id="m_releaseCoins" class="form-control">
<option>1</option>
<option selected>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
<option>11</option>
<option>12</option>
<option>13</option>
<option>14</option>
<option>15</option>
</select>
</div>
</div>

<br>

<div id="m_multiSigErrorMsg" class="alert alert-danger" style="display:none;"></div>

<div class="alert alert-success hidden" id="m_multiSigData">
<label>Address</label>
<p>Payment should be made to this address:</p>
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<input type="text" class="form-control address" value="" readonly>
<span class="input-group-btn">
<button class="qrcodeBtn btn btn-default" type="button" data-toggle="modal" data-target="#modalQrcode"><span class="glyphicon glyphicon-qrcode"></span></button>
</span>
</div>
</div>
</div>
<label>Redeem Script</label>
<p>This script should be <i>saved and should be shared with all the participants before a payment is made</i>, so they may validate the authenticity of the address, it will also be used later to release the bitcoins.</p>
<textarea class="form-control script" style="height:160px" readonly></textarea>
<label>Shareable URL</label>
<input type="text" class="scriptUrl form-control" disabled>
</div>

<input type="button" class="btn btn-primary" value="Submit" id="newMandatoryMultiSigAddress">
<br>
</div>

<div class="tab-pane tab-content" id="newHDaddress">
<h2>New HD Address <small>making bip32 even easier</small></h2>
<p>Use the form below to generate a <i>master</i> hierarchical deterministic address.</p>
Expand Down Expand Up @@ -599,10 +699,17 @@ <h4>Redeem Script</h4>
<label>Required Signatures</label>
<p class="signaturesRequired">?</p>
<label>Signatures Required from</label>
<table class="table table-striped table-hover">
<table id="verifyRsDataPubKeys" class="table table-striped table-hover">
<tbody>
</tbody>
</table>
<div id="verifyRsDataMandatory" class="hidden">
<label>Mandatory Signatures Required from</label>
<table id="verifyRsDataMandatoryKeys" class="table table-striped table-hover">
<tbody>
</tbody>
</table>
</div>
</div>

<div class="hidden verifyData" id="verifyTransactionData">
Expand Down
94 changes: 77 additions & 17 deletions js/coin.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,17 @@

/* new multisig address, provide the pubkeys AND required signatures to release the funds */
coinjs.pubkeys2MultisigAddress = function(pubkeys, required) {
return coinjs.pubkeysAndMandatory2MultisigAddress([], pubkeys, required);
}

/* new multisig address with some mandatory signment, provide the mandatory pubkeys, the multisig pubkeys
AND required signatures (of the multisig pubkeys) to release the funds */
coinjs.pubkeysAndMandatory2MultisigAddress = function(mandatorykeys, pubkeys, required) {
var s = coinjs.script();
for(var i = 0; i < mandatorykeys.length; ++i) {
s.writeBytes(Crypto.util.hexToBytes(mandatorykeys[i]));
s.writeOp(173); // OP_CHECKSIGVERIFY
}
s.writeOp(81 + (required*1) - 1); //OP_1
for (var i = 0; i < pubkeys.length; ++i) {
s.writeBytes(Crypto.util.hexToBytes(pubkeys[i]));
Expand Down Expand Up @@ -195,7 +205,7 @@
if(o.version==coinjs.pub){ // standard address
o.type = 'standard';

} else if (o.version==coinjs.multisig) { // multisig address
} else if (o.version==coinjs.multisig) { // multisig address or mandatory multisig
o.type = 'multisig';

} else if (o.version==coinjs.priv){ // wifkey
Expand Down Expand Up @@ -638,20 +648,50 @@
r.decodeRedeemScript = function(script){
var r = false;
try {
var s = coinjs.script(Crypto.util.hexToBytes(script));
if((s.chunks.length>=3) && s.chunks[s.chunks.length-1] == 174){//OP_CHECKMULTISIG
var info = coinjs.script(Crypto.util.hexToBytes(script)).listMultisigKeys();
if(info) {
r = {};
r.signaturesRequired = s.chunks[0]-80;
r.pubkeys = info.pubkeys;
r.mandatorykeys = info.mandatorykeys;
r.signaturesRequired = info.signaturesRequired;
r.address = coinjs.pubkeysAndMandatory2MultisigAddress(r.mandatorykeys, r.pubkeys, r.signaturesRequired).address;
}
} catch(e) {
// console.log(e);
r = false;
}
return r;
}

/* retrieves the mandatory keys and multisig public keys from a multisig script */
r.listMultisigKeys = function() {
var r = false;
var s = this;
try {
if((s.chunks.length>=3) && s.chunks[s.chunks.length-1] == 174){//OP_CHECKMULTISIG
var npubs = s.chunks[s.chunks.length-2] - 80;
var nsigs = s.chunks[s.chunks.length-3-npubs] - 80;

var pubkeys = [];
for(var i=1;i<s.chunks.length-2;i++){
pubkeys.push(Crypto.util.bytesToHex(s.chunks[i]));
for(var i=1;i<=npubs;i++){
var key = s.chunks[(s.chunks.length-3-npubs) + i];
pubkeys.push(Crypto.util.bytesToHex(key));
}

var mandatorykeys = [];
if(s.chunks.length > (3 + npubs) && s.chunks[1] == 173){// OP_CHECKSIGVERIFY
for(var i=0; i < s.chunks.length-3-npubs; i += 2){
var key = s.chunks[i];
mandatorykeys.push(Crypto.util.bytesToHex(key));
}
}

r = {};
r.pubkeys = pubkeys;
var multi = coinjs.pubkeys2MultisigAddress(pubkeys, r.signaturesRequired);
r.address = multi['address'];
r.mandatorykeys = mandatorykeys;
r.signaturesRequired = nsigs;
}
} catch(e) {
// console.log(e);
r = false;
}
return r;
Expand Down Expand Up @@ -904,8 +944,10 @@
} else if (this.ins[index].script.chunks[0]==0 && this.ins[index].script.chunks[this.ins[index].script.chunks.length-1][this.ins[index].script.chunks[this.ins[index].script.chunks.length-1].length-1]==174) { // OP_CHECKMULTISIG
// multisig script, with signature(s) included
return {'type':'multisig', 'signed':'true', 'signatures':this.ins[index].script.chunks.length-2, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[this.ins[index].script.chunks.length-1])};
} else if (this.ins[index].script.chunks[0]>=80 && this.ins[index].script.chunks[this.ins[index].script.chunks.length-1]==174) { // OP_CHECKMULTISIG
// multisig script, without signature!
} else if (this.ins[index].script.chunks.length >= 3 && this.ins[index].script.chunks[this.ins[index].script.chunks.length-1]==174 // OP_CHECKMULTISIG
&& ((this.ins[index].script.chunks[1] == 173 && this.ins[index].script.chunks[0] instanceof Array)//OP_CHECKSIGVERIFY
|| this.ins[index].script.chunks[0]>=80)) {
// multisig script, without signature! // multisig script, without signature!
return {'type':'multisig', 'signed':'false', 'signatures':0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
} else if (this.ins[index].script.chunks.length==0) {
// empty
Expand Down Expand Up @@ -1052,12 +1094,21 @@
/* sign a multisig input */
r.signmultisig = function(index, wif){

function scriptListPubkey(redeemScript){
var r = {};
for(var i=1;i<redeemScript.chunks.length-2;i++){
r[i] = Crypto.util.hexToBytes(coinjs.pubkeydecompress(Crypto.util.bytesToHex(redeemScript.chunks[i])));
function scriptListKeys(redeemScript){
var info = redeemScript.listMultisigKeys();

var pubkeys = {};
for(var i=0; i < info.pubkeys.length; i++){
pubkeys[i+1] = Crypto.util.hexToBytes(coinjs.pubkeydecompress(info.pubkeys[i]));
}
return r;

var mandatorykeys = {};
for(var i=0; i < info.mandatorykeys.length; i++){
var idx = info.mandatorykeys.length - i; // mandatorykeys must be signed the other way around
mandatorykeys[idx] = Crypto.util.hexToBytes(coinjs.pubkeydecompress(info.mandatorykeys[i]));
}

return {'pubkeys': pubkeys, 'mandatorykeys': mandatorykeys};
}

function scriptListSigs(scriptSig){
Expand All @@ -1081,7 +1132,9 @@
s.writeBytes(signature);

} else if (this.ins[index].script.chunks[0]==0 && this.ins[index].script.chunks[this.ins[index].script.chunks.length-1][this.ins[index].script.chunks[this.ins[index].script.chunks.length-1].length-1]==174){
var pubkeyList = scriptListPubkey(coinjs.script(redeemScript));
var pkeysList = scriptListKeys(coinjs.script(redeemScript));
var pubkeyList = pkeysList.pubkeys;
var mandatorykeyList = pkeysList.mandatorykeys;
var sigsList = scriptListSigs(this.ins[index].script);
sigsList[coinjs.countObject(sigsList)+1] = signature;

Expand All @@ -1093,6 +1146,13 @@
}
}

for(x in mandatorykeyList){
for(y in sigsList){
if(coinjs.verifySignature(sighash, sigsList[y], mandatorykeyList[x])){
s.writeBytes(sigsList[y]);
}
}
}
}

s.writeBytes(redeemScript);
Expand Down
Loading