Simple Hodl Address

This commit is contained in:
Denilson M. Amorim 2015-12-27 00:37:51 -03:00
parent 9a669885b3
commit c8577e03be
8 changed files with 542 additions and 30 deletions

5
css/bootstrap-datetimepicker.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -12,10 +12,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/bootstrap.min.css" media="screen">
<link rel="stylesheet" href="css/bootstrap-datetimepicker.min.css">
<link rel="stylesheet" href="css/style.css" media="screen">
<script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="js/moment.min.js"></script>
<script type="text/javascript" src="js/transition.js"></script>
<script type="text/javascript" src="js/collapse.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/bootstrap-datetimepicker.min.js"></script>
<script type="text/javascript" src="js/crypto-min.js"></script>
<script type="text/javascript" src="js/crypto-sha256.js"></script>
@ -54,6 +59,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="#newTimeLocked" data-toggle="tab">New Time Locked 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>
@ -377,6 +383,73 @@
<br>
</div>
<div class="tab-pane tab-content" id="newTimeLocked">
<h2>New Time Locked Address <small>Coins can be released only after a certain date</small></h2>
<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 key that will be able to unlock the funds after the a certain date.
</div>
<div class="col-md-1">
</div>
</div>
<div class="row">
<div class="form-horizontal">
<div class="col-xs-11">
<input id="timeLockedPubKey" type="text" class="form-control pubkey">
</div>
</div>
</div>
<p>Enter the date and time required to release the coins</p>
<div class="row">
<div class='col-xs-3'>
<div class="form-group">
<div class='input-group date' id='timeLockedDateTimePicker'>
<input type='text' class="form-control" />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
</div>
<div class='col-xs-3'>
<small>System Local Time</small>
</div>
</div>
<br>
<div id="timeLockedErrorMsg" class="alert alert-danger" style="display:none;"></div>
<div class="alert alert-success hidden" id="timeLockedData">
<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" readonly>
</div>
<input type="button" class="btn btn-primary" value="Submit" id="newTimeLockedAddress">
<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>
@ -585,24 +658,64 @@
<div class="hidden verifyData" id="verifyRsData">
<h4>Redeem Script</h4>
<p><span style="float:right"><a href="javascript:;" target="_blank" class="verifyLink" title="Link to this page"><span class="glyphicon glyphicon-link"></span></a></span>The above redeem script has been decoded</p>
<label>Multi Signature Address</label>
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<input type="text" class="form-control address multisigAddress" 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>Required Signatures</label>
<p class="signaturesRequired">?</p>
<label>Signatures Required from</label>
<table class="table table-striped table-hover">
<tbody>
</tbody>
</table>
<div class="hidden" id="verifyRsDataMultisig">
<label>Multi Signature Address</label>
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<input type="text" class="form-control address multisigAddress" 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>Required Signatures</label>
<p class="signaturesRequired">?</p>
<label>Signatures Required from</label>
<table class="table table-striped table-hover">
<tbody>
</tbody>
</table>
</div>
<div class="hidden" id="verifyRsDataHodl">
<label>Hodl Address</label>
<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>Required Signature</label>
<div class="row">
<div class="col-lg-6">
<div class="input-group">
<input type="text" class="form-control pubkey" 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>Unlock Time</label>
<div class="row">
<div class='col-lg-6'>
<div class="input-group">
<input type="text" class="form-control date" value="" readonly>
</div>
</div>
</div>
<br>
</div>
</div>
<div class="hidden verifyData" id="verifyTransactionData">

9
js/bootstrap-datetimepicker.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -135,6 +135,36 @@
return {'address':address, 'redeemScript':redeemScript};
}
/* new time locked address, provide the pubkey and time necessary to unlock the funds.
when time is greater than 500000000, it should be a unix timestamp (seconds since epoch),
otherwise it should be the block height required before this transaction can be released.
may throw a string on failure!
*/
coinjs.simpleHodlAddress = function(pubkey, locktime) {
if(locktime < 0) {
throw "Locktime is negative.";
}
var s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(locktime.toString(16)));
s.writeOp(177);//OP_CHECKLOCKTIMEVERIFY
s.writeOp(117);//OP_DROP
s.writeBytes(Crypto.util.hexToBytes(pubkey));
s.writeOp(172);//OP_CHECKSIG
var x = ripemd160(Crypto.SHA256(s.buffer, {asBytes: true}), {asBytes: true});
x.unshift(coinjs.multisig);
var r = x;
r = Crypto.SHA256(Crypto.SHA256(r, {asBytes: true}), {asBytes: true});
var checksum = r.slice(0,4);
var redeemScript = Crypto.util.bytesToHex(s.buffer);
var address = coinjs.base58encode(x.concat(checksum));
return {'address':address, 'redeemScript':redeemScript};
}
/* provide a privkey and return an WIF */
coinjs.privkey2wif = function(h){
var r = Crypto.util.hexToBytes(h);
@ -649,6 +679,14 @@
r.pubkeys = pubkeys;
var multi = coinjs.pubkeys2MultisigAddress(pubkeys, r.signaturesRequired);
r.address = multi['address'];
r.type = 'multisig__'; // using __ for now to differentiat from the other object .type == "multisig"
} else if(s.chunks.length == 5 && s.chunks[1] == 177 && s.chunks[2] == 117 && s.chunks[4] == 172){
// ^ <locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP <pubkey> OP_CHECKSIG ^
r = {}
r.pubkey = Crypto.util.bytesToHex(s.chunks[3]);
r.locktime = parseInt(Crypto.util.bytesToHex(s.chunks[0]), 16); // TODO is this endian safe?
r.address = coinjs.simpleHodlAddress(r.pubkey, r.locktime).address;
r.type = "hodl__";
}
} catch(e) {
// console.log(e);
@ -898,9 +936,15 @@
if((this.ins[index].script.chunks.length==5) && this.ins[index].script.chunks[4]==172 && coinjs.isArray(this.ins[index].script.chunks[2])){ //OP_CHECKSIG
// regular scriptPubkey (not signed)
return {'type':'scriptpubkey', 'signed':'false', 'signatures':0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
} else if((this.ins[index].script.chunks.length==2) && this.ins[index].script.chunks[0][0]==48 && this.ins[index].script.chunks[1].length == 5 && this.ins[index].script.chunks[1][1]==177){//OP_CHECKLOCKTIMEVERIFY
// hodl script (signed)
return {'type':'hodl', 'signed':'true', 'signatures':1, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
} else if((this.ins[index].script.chunks.length==2) && this.ins[index].script.chunks[0][0]==48){
// regular scriptPubkey (probably signed)
return {'type':'scriptpubkey', 'signed':'true', 'signatures':1, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
} else if(this.ins[index].script.chunks.length == 5 && this.ins[index].script.chunks[1] == 177){//OP_CHECKLOCKTIMEVERIFY
// hodl script (not signed)
return {'type':'hodl', 'signed':'false', 'signatures': 0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)};
} 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])};
@ -1048,6 +1092,17 @@
this.ins[index].script = s;
return true;
}
/* signs a time locked / hodl input */
r.signhodl = function(index, wif){
var signature = this.transactionSig(index, wif);
var redeemScript = this.ins[index].script.buffer
var s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(signature));
s.writeBytes(redeemScript);
this.ins[index].script = s;
return true;
}
/* sign a multisig input */
r.signmultisig = function(index, wif){
@ -1111,6 +1166,8 @@
if(((d['type'] == 'scriptpubkey' && d['script']==Crypto.util.bytesToHex(pubkeyHash.buffer)) || d['type'] == 'empty') && d['signed'] == "false"){
this.signinput(i, wif);
} else if (d['type'] == 'hodl' && d['signed'] == "false") {
this.signhodl(i, wif);
} else if (d['type'] == 'multisig') {
this.signmultisig(i, wif);
} else {

View file

@ -360,6 +360,43 @@ $(document).ready(function() {
}
});
/* new -> time locked code */
$('#timeLockedDateTimePicker').datetimepicker({
format: "MM/DD/YYYY HH:mm",
});
$("#newTimeLockedAddress").click(function(){
$("#timeLockedData").removeClass('show').addClass('hidden').fadeOut();
$("#timeLockedPubKey").parent().removeClass('has-error');
$("#timeLockedDateTimePicker").parent().removeClass('has-error');
$("#timeLockedErrorMsg").hide();
if(!coinjs.pubkeydecompress($("#timeLockedPubKey").val())) {
$('#timeLockedPubKey').parent().addClass('has-error');
}
var date = $('#timeLockedDateTimePicker').data("DateTimePicker").date();
if(!date || !date.isValid()) {
$('#timeLockedDateTimePicker').parent().addClass('has-error');
}
if(($("#timeLockedPubKey").parent().hasClass('has-error')==false) && $("#timeLockedDateTimePicker").parent().hasClass('has-error')==false){
try {
var hodl = coinjs.simpleHodlAddress($("#timeLockedPubKey").val(), date.unix());
$("#timeLockedData .address").val(hodl['address']);
$("#timeLockedData .script").val(hodl['redeemScript']);
$("#timeLockedData .scriptUrl").val(document.location.origin+''+document.location.pathname+'?verify='+hodl['redeemScript']+'#verify');
$("#timeLockedData").removeClass('hidden').addClass('show').fadeIn();
} catch(e) {
$("#timeLockedErrorMsg").html('<span class="glyphicon glyphicon-exclamation-sign"></span> ' + e).fadeIn();
}
} else {
$("#timeLockedErrorMsg").html('<span class="glyphicon glyphicon-exclamation-sign"></span> Public key and/or date is invalid!').fadeIn();
}
});
/* new -> Hd address code */
$(".deriveHDbtn").click(function(){
@ -941,20 +978,34 @@ $(document).ready(function() {
var script = coinjs.script();
var decode = script.decodeRedeemScript($("#verifyScript").val());
if(decode){
$("#verifyRsData .multisigAddress").val(decode['address']);
$("#verifyRsData .signaturesRequired").html(decode['signaturesRequired']);
$("#verifyRsData table tbody").html("");
for(var i=0;i<decode.pubkeys.length;i++){
var pubkey = decode.pubkeys[i];
var address = coinjs.pubkey2address(pubkey);
$('<tr><td width="30%"><input type="text" class="form-control" value="'+address+'" readonly></td><td><input type="text" class="form-control" value="'+pubkey+'" readonly></td></tr>').appendTo("#verifyRsData table tbody");
$("#verifyRsDataMultisig").addClass('hidden');
$("#verifyRsDataHodl").addClass('hidden');
if(decode.type == "multisig__") {
$("#verifyRsDataMultisig .multisigAddress").val(decode['address']);
$("#verifyRsDataMultisig .signaturesRequired").html(decode['signaturesRequired']);
$("#verifyRsDataMultisig table tbody").html("");
for(var i=0;i<decode.pubkeys.length;i++){
var pubkey = decode.pubkeys[i];
var address = coinjs.pubkey2address(pubkey);
$('<tr><td width="30%"><input type="text" class="form-control" value="'+address+'" readonly></td><td><input type="text" class="form-control" value="'+pubkey+'" readonly></td></tr>').appendTo("#verifyRsDataMultisig table tbody");
}
$("#verifyRsData").removeClass("hidden");
$("#verifyRsDataMultisig").removeClass('hidden');
$(".verifyLink").attr('href','?verify='+$("#verifyScript").val());
return true;
} else if(decode.type == "hodl__") {
var d = $("#verifyRsDataHodl .date").data("DateTimePicker");
$("#verifyRsDataHodl .address").val(decode['address']);
$("#verifyRsDataHodl .pubkey").val(coinjs.pubkey2address(decode['pubkey']));
$("#verifyRsDataHodl .date").val(decode['locktime'] >= 500000000? moment.unix(decode['locktime']).format("MM/DD/YYYY HH:mm") : decode['locktime']);
$("#verifyRsData").removeClass("hidden");
$("#verifyRsDataHodl").removeClass('hidden');
$(".verifyLink").attr('href','?verify='+$("#verifyScript").val());
return true;
}
$("#verifyRsData").removeClass("hidden");
$(".verifyLink").attr('href','?verify='+$("#verifyScript").val());
return true;
} else {
return false;
}
return false;
}
function decodeTransactionScript(){

211
js/collapse.js Normal file
View file

@ -0,0 +1,211 @@
/* ========================================================================
* Bootstrap: collapse.js v3.3.4
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// COLLAPSE PUBLIC CLASS DEFINITION
// ================================
var Collapse = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Collapse.DEFAULTS, options)
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
this.transitioning = null
if (this.options.parent) {
this.$parent = this.getParent()
} else {
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
}
if (this.options.toggle) this.toggle()
}
Collapse.VERSION = '3.3.4'
Collapse.TRANSITION_DURATION = 350
Collapse.DEFAULTS = {
toggle: true
}
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
}
Collapse.prototype.show = function () {
if (this.transitioning || this.$element.hasClass('in')) return
var activesData
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
if (actives && actives.length) {
activesData = actives.data('bs.collapse')
if (activesData && activesData.transitioning) return
}
var startEvent = $.Event('show.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
if (actives && actives.length) {
Plugin.call(actives, 'hide')
activesData || actives.data('bs.collapse', null)
}
var dimension = this.dimension()
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
.attr('aria-expanded', true)
this.$trigger
.removeClass('collapsed')
.attr('aria-expanded', true)
this.transitioning = 1
var complete = function () {
this.$element
.removeClass('collapsing')
.addClass('collapse in')[dimension]('')
this.transitioning = 0
this.$element
.trigger('shown.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
this.$element
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
}
Collapse.prototype.hide = function () {
if (this.transitioning || !this.$element.hasClass('in')) return
var startEvent = $.Event('hide.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
var dimension = this.dimension()
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
this.$element
.addClass('collapsing')
.removeClass('collapse in')
.attr('aria-expanded', false)
this.$trigger
.addClass('collapsed')
.attr('aria-expanded', false)
this.transitioning = 1
var complete = function () {
this.transitioning = 0
this.$element
.removeClass('collapsing')
.addClass('collapse')
.trigger('hidden.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
this.$element
[dimension](0)
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
}
Collapse.prototype.toggle = function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']()
}
Collapse.prototype.getParent = function () {
return $(this.options.parent)
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
.each($.proxy(function (i, element) {
var $element = $(element)
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
}, this))
.end()
}
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
var isOpen = $element.hasClass('in')
$element.attr('aria-expanded', isOpen)
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen)
}
function getTargetFromTrigger($trigger) {
var href
var target = $trigger.attr('data-target')
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
return $(target)
}
// COLLAPSE PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.collapse
$.fn.collapse = Plugin
$.fn.collapse.Constructor = Collapse
// COLLAPSE NO CONFLICT
// ====================
$.fn.collapse.noConflict = function () {
$.fn.collapse = old
return this
}
// COLLAPSE DATA-API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
var $this = $(this)
if (!$this.attr('data-target')) e.preventDefault()
var $target = getTargetFromTrigger($this)
var data = $target.data('bs.collapse')
var option = data ? 'toggle' : $this.data()
Plugin.call($target, option)
})
}(jQuery);

7
js/moment.min.js vendored Normal file

File diff suppressed because one or more lines are too long

59
js/transition.js Normal file
View file

@ -0,0 +1,59 @@
/* ========================================================================
* Bootstrap: transition.js v3.3.4
* http://getbootstrap.com/javascript/#transitions
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap')
var transEndEventNames = {
WebkitTransition : 'webkitTransitionEnd',
MozTransition : 'transitionend',
OTransition : 'oTransitionEnd otransitionend',
transition : 'transitionend'
}
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] }
}
}
return false // explicit for ie8 ( ._.)
}
// http://blog.alexmaccaw.com/css-transitions
$.fn.emulateTransitionEnd = function (duration) {
var called = false
var $el = this
$(this).one('bsTransitionEnd', function () { called = true })
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
setTimeout(callback, duration)
return this
}
$(function () {
$.support.transition = transitionEnd()
if (!$.support.transition) return
$.event.special.bsTransitionEnd = {
bindType: $.support.transition.end,
delegateType: $.support.transition.end,
handle: function (e) {
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
}
}
})
}(jQuery);