From 07eb3669c47089d7f2dba869d7c6d2bce1202cf8 Mon Sep 17 00:00:00 2001 From: Chuck Date: Sun, 10 Nov 2013 02:07:30 +0700 Subject: [PATCH 01/21] Implementing P2SH multisignature transactions --- README | 9 +- css/brainwallet.css | 16 + index.html | 228 +++---------- js/bitcoinjs-min.js | 2 +- js/brainwallet.js | 771 +++++++++----------------------------------- js/tx.js | 56 ++-- 6 files changed, 251 insertions(+), 831 deletions(-) diff --git a/README b/README index 75ecbd0..de5cab1 100644 --- a/README +++ b/README @@ -1,13 +1,12 @@ -JavaScript Client-Side Bitcoin Address Generator +JavaScript Client-Side Multisignature P2SH Address and Transaction Generator -http://brainwallet.github.com +http://github.com/sarchar/brainwallet.github.com Notable features: +- Create Multisignature P2SH Addresses (supports 1/2/3 of 3) +- Spend from P2SH multisignature outputs - Online converter, including Base58 decoder and encoder - OpenSSL point conversion and compressed keys support -- Armory and Electrum deterministic wallets implementation - RFC 1751 JavaScript implementation - Bitcoin transactions editor -- Signing and verifying messages with bitcoin address -- Litecoin support diff --git a/css/brainwallet.css b/css/brainwallet.css index da48dd6..d870048 100644 --- a/css/brainwallet.css +++ b/css/brainwallet.css @@ -4,3 +4,19 @@ body { } .form-control[disabled], .form-control[readonly] { cursor: auto; } + +hidden { + display: none; +} + +.col-lg-2 { + width: 18%; +} + +.col-lg-offset-2 { + margin-left: 18%; +} + +.col-lg-10 { + width: 82%; +} diff --git a/index.html b/index.html index 53cac38..7418cbd 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - Brainwallet - JavaScript Client-Side Bitcoin Address Generator + Multi-Signature Brainwallet - JavaScript Client-Side Bitcoin Address Generator @@ -26,23 +26,21 @@
- +
- Generator + Create Multisignature P2SH
- +
-
- -
    +
+ + + +
   
- + +
+
+ + + +
    +
+
+
+
- +
-
- +
+
-
- -
- -
+
+
-
- +
+
-
- +
+
- +
- +
@@ -116,107 +124,35 @@
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
+
+
- Chains -
- -
-
- -
    -
-
-
- -
-
- -
- -
-
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
+ Spend Multisignature P2SH
- +
-
- -
+
-
- +
+
- +
-
-
-
-
-
-
- Transactions -
- +
+
-
- -
+
-
- +
+
- +
@@ -224,7 +160,7 @@
- +
BTC @@ -337,71 +273,11 @@
-
-
-
-
- Sign Message -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
-
-
-
-
-
- Verify Message -
- -
- -
-
-
- -
- -
-
-
-
-     -
-
-
-

diff --git a/js/bitcoinjs-min.js b/js/bitcoinjs-min.js index 5124d8d..3837b65 100644 --- a/js/bitcoinjs-min.js +++ b/js/bitcoinjs-min.js @@ -16,7 +16,7 @@ function ECFieldElementFp(e,t){this.x=t,this.q=e}function feFpEquals(e){return e function X9ECParameters(e,t,n,r){this.curve=e,this.g=t,this.n=n,this.h=r}function x9getCurve(){return this.curve}function x9getG(){return this.g}function x9getN(){return this.n}function x9getH(){return this.h}function fromHex(e){return new BigInteger(e,16)}function secp128r1(){var e=fromHex("FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF"),t=fromHex("FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC"),n=fromHex("E87579C11079F43DD824993C2CEE5ED3"),r=fromHex("FFFFFFFE0000000075A30D1B9038A115"),i=BigInteger.ONE,s=new ECCurveFp(e,t,n),o=s.decodePointHex("04161FF7528B899B2D0C28607CA52C5B86CF5AC8395BAFEB13C02DA292DDED7A83");return new X9ECParameters(s,o,r,i)}function secp160k1(){var e=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73"),t=BigInteger.ZERO,n=fromHex("7"),r=fromHex("0100000000000000000001B8FA16DFAB9ACA16B6B3"),i=BigInteger.ONE,s=new ECCurveFp(e,t,n),o=s.decodePointHex("043B4C382CE37AA192A4019E763036F4F5DD4D7EBB938CF935318FDCED6BC28286531733C3F03C4FEE");return new X9ECParameters(s,o,r,i)}function secp160r1(){var e=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF"),t=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC"),n=fromHex("1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45"),r=fromHex("0100000000000000000001F4C8F927AED3CA752257"),i=BigInteger.ONE,s=new ECCurveFp(e,t,n),o=s.decodePointHex("044A96B5688EF573284664698968C38BB913CBFC8223A628553168947D59DCC912042351377AC5FB32");return new X9ECParameters(s,o,r,i)}function secp192k1(){var e=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37"),t=BigInteger.ZERO,n=fromHex("3"),r=fromHex("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D"),i=BigInteger.ONE,s=new ECCurveFp(e,t,n),o=s.decodePointHex("04DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D");return new X9ECParameters(s,o,r,i)}function secp192r1(){var e=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF"),t=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC"),n=fromHex("64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1"),r=fromHex("FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831"),i=BigInteger.ONE,s=new ECCurveFp(e,t,n),o=s.decodePointHex("04188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF101207192B95FFC8DA78631011ED6B24CDD573F977A11E794811");return new X9ECParameters(s,o,r,i)}function secp224r1(){var e=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001"),t=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE"),n=fromHex("B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4"),r=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D"),i=BigInteger.ONE,s=new ECCurveFp(e,t,n),o=s.decodePointHex("04B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34");return new X9ECParameters(s,o,r,i)}function secp256k1(){var e=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"),t=BigInteger.ZERO,n=fromHex("7"),r=fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),i=BigInteger.ONE,s=new ECCurveFp(e,t,n),o=s.decodePointHex("0479BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8");return new X9ECParameters(s,o,r,i)}function secp256r1(){var e=fromHex("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF"),t=fromHex("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC"),n=fromHex("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B"),r=fromHex("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"),i=BigInteger.ONE,s=new ECCurveFp(e,t,n),o=s.decodePointHex("046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C2964FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5");return new X9ECParameters(s,o,r,i)}function getSECCurveByName(e){return e=="secp128r1"?secp128r1():e=="secp160k1"?secp160k1():e=="secp160r1"?secp160r1():e=="secp192k1"?secp192k1():e=="secp192r1"?secp192r1():e=="secp224r1"?secp224r1():e=="secp256k1"?secp256k1():e=="secp256r1"?secp256r1():null}X9ECParameters.prototype.getCurve=x9getCurve,X9ECParameters.prototype.getG=x9getG,X9ECParameters.prototype.getN=x9getN,X9ECParameters.prototype.getH=x9getH; var EventEmitter=function(){};EventEmitter.prototype.on=function(e,t,n){n||(n=this),this._listeners||(this._listeners={}),this._listeners[e]||(this._listeners[e]=[]),this._unbinders||(this._unbinders={}),this._unbinders[e]||(this._unbinders[e]=[]);var r=function(e){t.apply(n,[e])};this._unbinders[e].push(t),this._listeners[e].push(r)},EventEmitter.prototype.trigger=function(e,t){t===undefined&&(t={}),this._listeners||(this._listeners={});if(!this._listeners[e])return;var n=this._listeners[e].length;while(n--)this._listeners[e][n](t)},EventEmitter.prototype.removeListener=function(e,t){this._unbinders||(this._unbinders={});if(!this._unbinders[e])return;var n=this._unbinders[e].length;while(n--)this._unbinders[e][n]===t&&(this._unbinders[e].splice(n,1),this._listeners[e].splice(n,1))},EventEmitter.augment=function(e){for(var t in EventEmitter.prototype)e[t]||(e[t]=EventEmitter.prototype[t])}; (function(e){var t=e;"object"!=typeof module&&(t.EventEmitter=EventEmitter)})("object"==typeof module?module.exports:window.Bitcoin={}); -BigInteger.valueOf=nbv,BigInteger.prototype.toByteArrayUnsigned=function(){var e=this.abs().toByteArray();return e.length?(e[0]==0&&(e=e.slice(1)),e.map(function(e){return e<0?e+256:e})):e},BigInteger.fromByteArrayUnsigned=function(e){return e.length?e[0]&128?new BigInteger([0].concat(e)):new BigInteger(e):e.valueOf(0)},BigInteger.prototype.toByteArraySigned=function(){var e=this.abs().toByteArrayUnsigned(),t=this.compareTo(BigInteger.ZERO)<0;return t?e[0]&128?e.unshift(128):e[0]|=128:e[0]&128&&e.unshift(0),e},BigInteger.fromByteArraySigned=function(e){return e[0]&128?(e[0]&=127,BigInteger.fromByteArrayUnsigned(e).negate()):BigInteger.fromByteArrayUnsigned(e)};var names=["log","debug","info","warn","error","assert","dir","dirxml","group","groupEnd","time","timeEnd","count","trace","profile","profileEnd"];"undefined"==typeof window.console&&(window.console={});for(var i=0;i>>8,e&255]:e<=1?[254].concat(Crypto.util.wordsToBytes([e])):[255].concat(Crypto.util.wordsToBytes([e>>>32,e]))},valueToBigInt:function(e){return e instanceof BigInteger?e:BigInteger.fromByteArrayUnsigned(e)},formatValue:function(e){var t=this.valueToBigInt(e).toString(),n=t.length>8?t.substr(0,t.length-8):"0",r=t.length>8?t.substr(t.length-8):t;while(r.length<8)r="0"+r;r=r.replace(/0*$/,"");while(r.length<2)r+="0";return n+"."+r},parseValue:function(e){var t=e.split("."),n=t[0],r=t[1]||"0";while(r.length<8)r+="0";r=r.replace(/^0+/g,"");var i=BigInteger.valueOf(parseInt(n));return i=i.multiply(BigInteger.valueOf(1e8)),i=i.add(BigInteger.valueOf(parseInt(r))),i},sha256ripe160:function(e){return Crypto.RIPEMD160(Crypto.SHA256(e,{asBytes:!0}),{asBytes:!0})}};for(var i in Crypto.util)Crypto.util.hasOwnProperty(i)&&(Bitcoin.Util[i]=Crypto.util[i]); +BigInteger.valueOf=nbv,BigInteger.prototype.toByteArrayUnsigned=function(){var e=this.abs().toByteArray();return e.length?(e[0]==0&&(e=e.slice(1)),e.map(function(e){return e<0?e+256:e})):e},BigInteger.fromByteArrayUnsigned=function(e){return e.length?e[0]&128?new BigInteger([0].concat(e)):new BigInteger(e):e.valueOf(0)},BigInteger.prototype.toByteArraySigned=function(){var e=this.abs().toByteArrayUnsigned(),t=this.compareTo(BigInteger.ZERO)<0;return t?e[0]&128?e.unshift(128):e[0]|=128:e[0]&128&&e.unshift(0),e},BigInteger.fromByteArraySigned=function(e){return e[0]&128?(e[0]&=127,BigInteger.fromByteArrayUnsigned(e).negate()):BigInteger.fromByteArrayUnsigned(e)};var names=["log","debug","info","warn","error","assert","dir","dirxml","group","groupEnd","time","timeEnd","count","trace","profile","profileEnd"];"undefined"==typeof window.console&&(window.console={});for(var i=0;i>>8]:e<=1?[254].concat(Crypto.util.wordsToBytes([e])):[255].concat(Crypto.util.wordsToBytes([e>>>32,e]))},valueToBigInt:function(e){return e instanceof BigInteger?e:BigInteger.fromByteArrayUnsigned(e)},formatValue:function(e){var t=this.valueToBigInt(e).toString(),n=t.length>8?t.substr(0,t.length-8):"0",r=t.length>8?t.substr(t.length-8):t;while(r.length<8)r="0"+r;r=r.replace(/0*$/,"");while(r.length<2)r+="0";return n+"."+r},parseValue:function(e){var t=e.split("."),n=t[0],r=t[1]||"0";while(r.length<8)r+="0";r=r.replace(/^0+/g,"");var i=BigInteger.valueOf(parseInt(n));return i=i.multiply(BigInteger.valueOf(1e8)),i=i.add(BigInteger.valueOf(parseInt(r))),i},sha256ripe160:function(e){return Crypto.RIPEMD160(Crypto.SHA256(e,{asBytes:!0}),{asBytes:!0})}};for(var i in Crypto.util)Crypto.util.hasOwnProperty(i)&&(Bitcoin.Util[i]=Crypto.util[i]); (function(e){e.Base58={alphabet:"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz",validRegex:/^[1-9A-HJ-NP-Za-km-z]+$/,base:BigInteger.valueOf(58),encode:function(e){var n=BigInteger.fromByteArrayUnsigned(e),r=[];while(n.compareTo(t.base)>=0){var i=n.mod(t.base);r.unshift(t.alphabet[i.intValue()]),n=n.subtract(i).divide(t.base)}r.unshift(t.alphabet[n.intValue()]);for(var s=0;s=0;i--){var s=t.alphabet.indexOf(e[i]);if(s<0)throw"Invalid character";n=n.add(BigInteger.valueOf(s).multiply(t.base.pow(e.length-1-i))),e[i]=="1"?r++:r=0}var o=n.toByteArrayUnsigned();while(r-->0)o.unshift(0);return o}};var t=e.Base58})("undefined"!=typeof Bitcoin?Bitcoin:module.exports); Bitcoin.Address=function(e){"string"==typeof e&&(e=Bitcoin.Address.decodeString(e)),this.hash=e,this.version=0},Bitcoin.Address.prototype.toString=function(){var e=this.hash.slice(0);e.unshift(this.version);var t=Crypto.SHA256(Crypto.SHA256(e,{asBytes:!0}),{asBytes:!0}),n=e.concat(t.slice(0,4));return Bitcoin.Base58.encode(n)},Bitcoin.Address.prototype.getHashBase64=function(){return Crypto.util.bytesToBase64(this.hash)},Bitcoin.Address.decodeString=function(e){var t=Bitcoin.Base58.decode(e),n=t.slice(0,21),r=Crypto.SHA256(Crypto.SHA256(n,{asBytes:!0}),{asBytes:!0});if(r[0]!=t[21]||r[1]!=t[22]||r[2]!=t[23]||r[3]!=t[24])throw"Checksum validation failed!";var i=n.shift();if(i!=0)throw"Version "+i+" not supported!";return n}; function integerToBytes(e,t){var n=e.toByteArrayUnsigned();if(tn.length)n.unshift(0);return n}function dmp(e){return e instanceof BigInteger||(e=e.toBigInteger()),Crypto.util.bytesToHex(e.toByteArrayUnsigned())}ECFieldElementFp.prototype.getByteLength=function(){return Math.floor((this.toBigInteger().bitLength()+7)/8)},ECPointFp.prototype.getEncoded=function(e){var t=this.getX().toBigInteger(),n=this.getY().toBigInteger(),r=integerToBytes(t,32);return e?n.isEven()?r.unshift(2):r.unshift(3):(r.unshift(4),r=r.concat(integerToBytes(n,32))),r},ECPointFp.decodeFrom=function(e,t){var n=t[0],r=t.length-1,i=t.slice(1,1+r/2),s=t.slice(1+r/2,1+r);i.unshift(0),s.unshift(0);var o=new BigInteger(i),u=new BigInteger(s);return new ECPointFp(e,e.fromBigInteger(o),e.fromBigInteger(u))},ECPointFp.prototype.add2D=function(e){if(this.isInfinity())return e;if(e.isInfinity())return this;if(this.x.equals(e.x))return this.y.equals(e.y)?this.twice():this.curve.getInfinity();var t=e.x.subtract(this.x),n=e.y.subtract(this.y),r=n.divide(t),i=r.square().subtract(this.x).subtract(e.x),s=r.multiply(this.x.subtract(i)).subtract(this.y);return new ECPointFp(this.curve,i,s)},ECPointFp.prototype.twice2D=function(){if(this.isInfinity())return this;if(this.y.toBigInteger().signum()==0)return this.curve.getInfinity();var e=this.curve.fromBigInteger(BigInteger.valueOf(2)),t=this.curve.fromBigInteger(BigInteger.valueOf(3)),n=this.x.square().multiply(t).add(this.curve.a).divide(this.y.multiply(e)),r=n.square().subtract(this.x.multiply(e)),i=n.multiply(this.x.subtract(r)).subtract(this.y);return new ECPointFp(this.curve,r,i)},ECPointFp.prototype.multiply2D=function(e){if(this.isInfinity())return this;if(e.signum()==0)return this.curve.getInfinity();var t=e,n=t.multiply(new BigInteger("3")),r=this.negate(),i=this,s;for(s=n.bitLength()-2;s>0;--s){i=i.twice();var o=n.testBit(s),u=t.testBit(s);o!=u&&(i=i.add2D(o?this:r))}return i},ECPointFp.prototype.isOnCurve=function(){var e=this.getX().toBigInteger(),t=this.getY().toBigInteger(),n=this.curve.getA().toBigInteger(),r=this.curve.getB().toBigInteger(),i=this.curve.getQ(),s=t.multiply(t).mod(i),o=e.multiply(e).multiply(e).add(n.multiply(e)).add(r).mod(i);return s.equals(o)},ECPointFp.prototype.toString=function(){return"("+this.getX().toBigInteger().toString()+","+this.getY().toBigInteger().toString()+")"},ECPointFp.prototype.validate=function(){var e=this.curve.getQ();if(this.isInfinity())throw new Error("Point is at infinity.");var t=this.getX().toBigInteger(),n=this.getY().toBigInteger();if(t.compareTo(BigInteger.ONE)<0||t.compareTo(e.subtract(BigInteger.ONE))>0)throw new Error("x coordinate out of bounds");if(n.compareTo(BigInteger.ONE)<0||n.compareTo(e.subtract(BigInteger.ONE))>0)throw new Error("y coordinate out of bounds");if(!this.isOnCurve())throw new Error("Point is not on the curve.");if(this.multiply(e).isInfinity())throw new Error("Point is not a scalar multiple of G.");return!0},Bitcoin.ECDSA=function(){function r(e,t,n,r){var i=Math.max(t.bitLength(),r.bitLength()),s=e.add2D(n),o=e.curve.getInfinity();for(var u=i-1;u>=0;--u)o=o.twice2D(),o.z=BigInteger.ONE,t.testBit(u)?r.testBit(u)?o=o.add2D(s):o=o.add2D(e):r.testBit(u)&&(o=o.add2D(n));return o}var e=getSECCurveByName("secp256k1"),t=new SecureRandom,n=null,i={getBigRandom:function(e){return(new BigInteger(e.bitLength(),t)).mod(e.subtract(BigInteger.ONE)).add(BigInteger.ONE)},sign:function(t,n){var r=n,s=e.getN(),o=BigInteger.fromByteArrayUnsigned(t);do var u=i.getBigRandom(s),a=e.getG(),f=a.multiply(u),l=f.getX().toBigInteger().mod(s);while(l.compareTo(BigInteger.ZERO)<=0);var c=u.modInverse(s).multiply(o.add(r.multiply(l))).mod(s);return i.serializeSig(l,c)},verify:function(t,n,r){var s,o;if(Bitcoin.Util.isArray(n)){var u=i.parseSig(n);s=u.r,o=u.s}else{if("object"!=typeof n||!n.r||!n.s)throw"Invalid value for signature";s=n.r,o=n.s}var a;if(r instanceof ECPointFp)a=r;else{if(!Bitcoin.Util.isArray(r))throw"Invalid format for pubkey value, must be byte array or ECPointFp";a=ECPointFp.decodeFrom(e.getCurve(),r)}var f=BigInteger.fromByteArrayUnsigned(t);return i.verifyRaw(f,s,o,a)},verifyRaw:function(t,n,r,i){var s=e.getN(),o=e.getG();if(n.compareTo(BigInteger.ONE)<0||n.compareTo(s)>=0)return!1;if(r.compareTo(BigInteger.ONE)<0||r.compareTo(s)>=0)return!1;var u=r.modInverse(s),a=t.multiply(u).mod(s),f=n.multiply(u).mod(s),l=o.multiply(a).add(i.multiply(f)),c=l.getX().toBigInteger().mod(s);return c.equals(n)},serializeSig:function(e,t){var n=e.toByteArraySigned(),r=t.toByteArraySigned(),i=[];return i.push(2),i.push(n.length),i=i.concat(n),i.push(2),i.push(r.length),i=i.concat(r),i.unshift(i.length),i.unshift(48),i},parseSig:function(e){var t;if(e[0]!=48)throw new Error("Signature not a valid DERSequence");t=2;if(e[t]!=2)throw new Error("First element in signature must be a DERInteger");var n=e.slice(t+2,t+2+e[t+1]);t+=2+e[t+1];if(e[t]!=2)throw new Error("Second element in signature must be a DERInteger");var r=e.slice(t+2,t+2+e[t+1]);t+=2+e[t+1];var i=BigInteger.fromByteArrayUnsigned(n),s=BigInteger.fromByteArrayUnsigned(r);return{r:i,s:s}},parseSigCompact:function(t){if(t.length!==65)throw"Signature has the wrong length";var n=t[0]-27;if(n<0||n>7)throw"Invalid signature type";var r=e.getN(),i=BigInteger.fromByteArrayUnsigned(t.slice(1,33)).mod(r),s=BigInteger.fromByteArrayUnsigned(t.slice(33,65)).mod(r);return{r:i,s:s,i:n}},recoverPubKey:function(t,s,o,u){u&=3;var a=u&1,f=u>>1,l=e.getN(),c=e.getG(),h=e.getCurve(),p=h.getQ(),d=h.getA().toBigInteger(),v=h.getB().toBigInteger();n||(n=p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)));var m=f?t.add(l):t,g=m.multiply(m).multiply(m).add(d.multiply(m)).add(v).mod(p),y=g.modPow(n,p),b=y.isEven()?u%2:(u+1)%2,w=(y.isEven()?!a:a)?y:p.subtract(y),E=new ECPointFp(h,h.fromBigInteger(m),h.fromBigInteger(w));E.validate();var S=BigInteger.fromByteArrayUnsigned(o),x=BigInteger.ZERO.subtract(S).mod(l),T=t.modInverse(l),N=r(E,s,c,x).multiply(T);console.log("G.x: ",Crypto.util.bytesToHex(c.x.toBigInteger().toByteArrayUnsigned())),console.log("G.y: ",Crypto.util.bytesToHex(c.y.toBigInteger().toByteArrayUnsigned())),console.log("s: ",Crypto.util.bytesToHex(T.toByteArrayUnsigned())),console.log("Q.x: ",Crypto.util.bytesToHex(N.x.toBigInteger().toByteArrayUnsigned())),console.log("Q.y: ",Crypto.util.bytesToHex(N.y.toBigInteger().toByteArrayUnsigned())),N.validate();if(!i.verifyRaw(S,t,s,N))throw"Pubkey recovery unsuccessful";var C=new Bitcoin.ECKey;return C.pub=N,C},calcPubkeyRecoveryParam:function(e,t,n,r){for(var i=0;i<4;i++)try{var s=Bitcoin.ECDSA.recoverPubKey(t,n,r,i);if(s.getBitcoinAddress().toString()==e)return i}catch(o){}throw"Unable to find valid recovery factor"}};return i}(); diff --git a/js/brainwallet.js b/js/brainwallet.js index ab7533b..7e27f40 100644 --- a/js/brainwallet.js +++ b/js/brainwallet.js @@ -1,6 +1,7 @@ (function($){ - var gen_from = 'pass'; + var req_count = 2; + var outof_count = 3; var gen_compressed = false; var gen_eckey = null; var gen_pt = null; @@ -136,123 +137,80 @@ } } - function genRandom() { - $('#pass').val(''); - $('#hash').focus(); - gen_from = 'hash'; - $('#from_hash').click(); - update_gen(); - var bytes = Crypto.util.randomBytes(32); - $('#hash').val(Crypto.util.bytesToHex(bytes)); - generate(); + function reqUpdateLabel() { + $('#reqMsg').text($('#req_'+req_count).parent().attr('title')); } - function update_gen() { - setErrorState($('#hash'), false); - setErrorState($('#sec'), false); - $('#pass').attr('readonly', gen_from != 'pass'); - $('#hash').attr('readonly', gen_from != 'hash'); - $('#sec').attr('readonly', gen_from != 'sec'); - $('#sec').parent().parent().removeClass('error'); - } - - function genUpdateLabel() { - $('#genMsg').text($('#from_'+gen_from).parent().attr('title')); + function update_req_count() { + req_count = parseInt($(this).attr('id').substring(4)); + reqUpdateLabel(); + clearTimeout(timeout); + timeout = setTimeout(generate_redemption_script, TIMEOUT); } - function update_gen_from() { - gen_from = $(this).attr('id').substring(5); - genUpdateLabel(); - update_gen(); - if (gen_from == 'pass') { - if (gen_ps_reset) { - gen_ps_reset = false; - onChangePass(); - } - $('#pass').focus(); - } else if (gen_from == 'hash') { - $('#hash').focus(); - } else if (gen_from == 'sec') { - $('#sec').focus(); - } + function update_outof() { + $("#pub1_group").removeClass('hidden'); + $("#pub2_group").removeClass('hidden').addClass((outof_count < 2) ? 'hidden' : ''); + $("#pub3_group").removeClass('hidden').addClass((outof_count < 3) ? 'hidden' : ''); } - function update_gen_from_focus() { - gen_from = $(this).attr('id'); - update_gen(); - if (gen_from == 'pass') { - if (gen_ps_reset) { - gen_ps_reset = false; - onChangePass(); - } - } - $('#from_'+gen_from).button('toggle'); - } - - function generate() { - var hash_str = pad($('#hash').val(), 64, '0'); - - var hash = Crypto.util.hexToBytes(hash_str); - - eckey = new Bitcoin.ECKey(hash); - - gen_eckey = eckey; - - try { - var curve = getSECCurveByName("secp256k1"); - gen_pt = curve.getG().multiply(eckey.priv); - gen_eckey.pub = getEncoded(gen_pt, gen_compressed); - gen_eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(gen_eckey.pub); - setErrorState($('#hash'), false); - } catch (err) { - //console.info(err); - setErrorState($('#hash'), true, 'Invalid secret exponent (must be non-zero value)'); - return; - } - - gen_update(); + function outofUpdateLabel() { + $('#outofMsg').text($('#outof_'+outof_count).parent().attr('title')); } - function update_gen_compressed() { - setErrorState($('#hash'), false); - setErrorState($('#sec'), false); - gen_compressed = $(this).attr('id') == 'compressed'; - gen_eckey.pub = getEncoded(gen_pt, gen_compressed); - gen_eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(gen_eckey.pub); - gen_update(); + function update_outof_count() { + outof_count = parseInt($(this).attr('id').substring(6)) + outofUpdateLabel(); + update_outof(); + clearTimeout(timeout); + timeout = setTimeout(generate_redemption_script, TIMEOUT); } - function gen_update() { + function generate_redemption_script() { + var pub1_str = pad($('#pub1').val(), 65, '0'); + var pub1 = Crypto.util.hexToBytes(pub1_str); - var eckey = gen_eckey; - var compressed = gen_compressed; + var pub2_str = pad($('#pub2').val(), 65, '0'); + var pub2 = Crypto.util.hexToBytes(pub2_str); - var hash_str = pad($('#hash').val(), 64, '0'); - var hash = Crypto.util.hexToBytes(hash_str); + var pub3_str = pad($('#pub3').val(), 65, '0'); + var pub3 = Crypto.util.hexToBytes(pub3_str); - var hash160 = eckey.getPubKeyHash(); + var pubkey1 = new Bitcoin.ECKey(); + pubkey1.pub = pub1; + pubkey1.pubKeyHash = Bitcoin.Util.sha256ripe160(pubkey1.pub); - var h160 = Crypto.util.bytesToHex(hash160); - $('#h160').val(h160); + var pubkey2 = new Bitcoin.ECKey(); + pubkey2.pub = pub2; + pubkey2.pubKeyHash = Bitcoin.Util.sha256ripe160(pubkey2.pub); - var addr = new Bitcoin.Address(hash160); - addr.version = PUBLIC_KEY_VERSION; - $('#addr').val(addr); + var pubkey3 = new Bitcoin.ECKey(); + pubkey3.pub = pub3; + pubkey3.pubKeyHash = Bitcoin.Util.sha256ripe160(pubkey3.pub); - var payload = hash; + // New versions of BitcoinJS-lib have createMultiSigOutputScript, but the one + // currently in brainwallet at github doesn't have it, so we must build the + // script manually. + var redemption_script = new Bitcoin.Script(); - if (compressed) - payload.push(0x01); + redemption_script.writeOp([Bitcoin.Opcode.map["OP_1"], Bitcoin.Opcode.map["OP_2"], Bitcoin.Opcode.map["OP_3"]][req_count - 1]); + + var pubkeys = new Array(pub1, pub2, pub3); + for( var i = 0; i < 3 && i < outof_count; i++ ) { + redemption_script.writeBytes(pubkeys[i]); + } - var sec = new Bitcoin.Address(payload); - sec.version = PRIVATE_KEY_VERSION; - $('#sec').val(sec); + redemption_script.writeOp(Bitcoin.Opcode.map["OP_1"] + (pubkeys.length - 1)); + redemption_script.writeOp(Bitcoin.Opcode.map["OP_CHECKMULTISIG"]); - var pub = Crypto.util.bytesToHex(getEncoded(gen_pt, compressed)); - $('#pub').val(pub); + var redemption_script_str = Crypto.util.bytesToHex(redemption_script.buffer); + $("#redemption_script").val(redemption_script_str); - var der = Crypto.util.bytesToHex(getDER(eckey, compressed)); - $('#der').val(der); + // Hash the script to produce the bitcoin address: + var redemptionScriptHash160 = Bitcoin.Util.sha256ripe160(redemption_script.buffer); + var p2sh_addr = new Bitcoin.Address(redemptionScriptHash160); + p2sh_addr.version = 5; + $("#addr").val('' + p2sh_addr); var qrCode = qrcode(3, 'M'); var text = $('#addr').val(); @@ -265,85 +223,18 @@ $('#genAddrURL').attr('title', addr); } - - function calc_hash() { - var hash = Crypto.SHA256($('#pass').val(), { asBytes: true }); - $('#hash').val(Crypto.util.bytesToHex(hash)); - } - - function onChangePass() { - calc_hash(); - clearTimeout(timeout); - timeout = setTimeout(generate, TIMEOUT); - } - - function onChangeHash() { - $('#pass').val(''); - gen_ps_reset = true; - clearTimeout(timeout); - - if (/[^0123456789abcdef]+/i.test($('#hash').val())) { - setErrorState($('#hash'), true, 'Erroneous characters (must be 0..9-a..f)'); - return; - } else { - setErrorState($('#hash'), false); - } - - timeout = setTimeout(generate, TIMEOUT); - } - - function onChangePrivKey() { - + function onChangePublicKey() { clearTimeout(timeout); - - $('#pass').val(''); - gen_ps_reset = true; - - var sec = $('#sec').val(); - - try { - var res = parseBase58Check(sec); - var version = res[0]; - var payload = res[1]; - } catch (err) { - setErrorState($('#sec'), true, 'Invalid private key checksum'); - return; - }; - - if (version != PRIVATE_KEY_VERSION) { - setErrorState($('#sec'), true, 'Invalid private key version'); - return; - } else if (payload.length < 32) { - setErrorState($('#sec'), true, 'Invalid payload (must be 32 or 33 bytes)'); - return; - } - - setErrorState($('#sec'), false); - - if (payload.length > 32) { - payload.pop(); - gen_compressed = true; - $('#compressed').button('toggle'); - } else { - gen_compressed = false; - $('#uncompressed').button('toggle'); - } - - $('#hash').val(Crypto.util.bytesToHex(payload)); - - timeout = setTimeout(generate, TIMEOUT); + timeout = setTimeout(generate_redemption_script, TIMEOUT); } - function genRandomPass() { - // chosen by fair dice roll - // guaranted to be random - $('#pass').val('correct horse battery staple'); - $('#from_pass').button('toggle'); - $('#pass').focus(); - gen_from = 'pass'; - update_gen(); - calc_hash(); - generate(); + function initializePublicKeys() { + $('#pub1').val('045b18a4e0153f0b4a8864976d0c3e55ff6845857f8ee818ca5aa22fbdcfaf51f408005b719d7226310d191f993960055681de48a12ff430b7ca2f6298fbae19d5'); + $('#pub2').val('04edb36cd12605e32115916817d064733513992abcb69c12c2a3fe8a349d1bfe52f164e801f3d16b6dc5caaf8a901490a0a5c30ccde5a2e19b7f8a345d968ebf53'); + $('#pub3').val('04757f731c485c1e387a110a2441965c93c699d03aecaa9447154acbc0c28c23044dca44dbc63304957ed36ca0a16b800a31cd2ca8732ca8a7c5709c646c538bcc'); + $('#pub1').focus(); + reqUpdateLabel(); + generate_redemption_script(); } // --- converter --- @@ -529,245 +420,31 @@ }); } - // --- chain --- - var chain_mode = 'csv'; - var addresses = []; - var chain_range = parseInt($('#range').val()); - var chain_type = 'chain_armory'; - - function onChangeMethod() { - var id = $(this).attr('id'); - - if (chain_type != id) { - $('#seed').val(''); - $('#expo').val(''); - $('#memo').val(''); - $('#chMsg').text(''); - $('#chain').text(''); - chOnStop(); - } - - $('#elChange').attr('disabled', id != 'chain_electrum'); - - chain_type = id; - } - - function onChangeFormat() { - chain_mode = $(this).attr('id'); - update_chain(); - } - - function addr_to_csv(i, r) { - return i + ', "' + r[0] +'", "' + r[1] +'"\n'; - } - - function update_chain() { - if (addresses.length == 0) - return; - var str = ''; - if (chain_mode == 'csv') { - for (var i = 0; i < addresses.length; i++) - str += addr_to_csv(i+1, addresses[i]); - - } else if (chain_mode == 'json') { - - var w = {}; - w['keys'] = []; - for (var i = 0; i < addresses.length; i++) - w['keys'].push({'addr':addresses[i][0],'sec':addresses[i][1]}); - str = JSON.stringify(w, null, 4); - } - $('#chain').text(str); - - chain_range = parseInt($('#range').val()); - var change = chain_type == 'chain_electrum' ? parseInt($('#elChange').val()) : 0; - - if (addresses.length >= chain_range+change) - chOnStop(); - - } - - function onChangeSeed() { - $('#expo').val(''); - $('#chMsg').text(''); - chOnStop(); - $('#memo').val( mn_encode(seed) ); - clearTimeout(timeout); - timeout = setTimeout(chain_generate, TIMEOUT); - } - - function onChangeMemo() { - var str = $('#memo').val(); - - if (str.length == 0) { - chOnStop(); - return; - } - - if (chain_type == 'chain_electrum') { - if (issubset(mn_words, str)) { - var seed = mn_decode(str); - $('#seed').val(seed); - } - } - - if (chain_type == 'chain_armory') { - var keys = armory_decode_keys(str); - if (keys != null) { - var cc = keys[1]; - var pk = keys[0]; - $('#seed').val(Crypto.util.bytesToHex(cc)); - $('#expo').val(Crypto.util.bytesToHex(pk)); - } - } - - clearTimeout(timeout); - timeout = setTimeout(chain_generate, TIMEOUT); - } - - function chOnPlay() { - var cc = Crypto.util.randomBytes(32); - var pk = Crypto.util.randomBytes(32); - - if (chain_type == 'chain_armory') { - $('#seed').val(Crypto.util.bytesToHex(cc)); - $('#expo').val(Crypto.util.bytesToHex(pk)); - var codes = armory_encode_keys(pk, cc); - $('#memo').val(codes); - } - - if (chain_type == 'chain_electrum') { - var seed = Crypto.util.bytesToHex(pk.slice(0,16)); - //nb! electrum doesn't handle trailing zeros very well - if (seed.charAt(0) == '0') seed = seed.substr(1); - $('#seed').val(seed); - var codes = mn_encode(seed); - $('#memo').val(codes); - } - chain_generate(); - } - - function chOnStop() { - Armory.stop(); - Electrum.stop(); - if (chain_type == 'chain_electrum') { - $('#chMsg').text(''); - } - } - - function onChangeRange() - { - if ( addresses.length==0 ) - return; - clearTimeout(timeout); - timeout = setTimeout(update_chain_range, TIMEOUT); - } - - function addr_callback(r) { - addresses.push(r); - $('#chain').append(addr_to_csv(addresses.length,r)); - } - - function electrum_seed_update(r, seed) { - $('#chMsg').text('key stretching: ' + r + '%'); - $('#expo').val(Crypto.util.bytesToHex(seed)); - } - - function electrum_seed_success(privKey) { - $('#chMsg').text(''); - $('#expo').val(Crypto.util.bytesToHex(privKey)); - var addChange = parseInt($('#elChange').val()); - Electrum.gen(chain_range, addr_callback, update_chain, addChange); - } - - function update_chain_range() { - chain_range = parseInt($('#range').val()); - - addresses = []; - $('#chain').text(''); - - if (chain_type == 'chain_electrum') { - var addChange = parseInt($('#elChange').val()); - Electrum.stop(); - Electrum.gen(chain_range, addr_callback, update_chain, addChange); - } - - if (chain_type == 'chain_armory') { - var codes = $('#memo').val(); - Armory.gen(codes, chain_range, addr_callback, update_chain); - } - } - - function chain_generate() { - clearTimeout(timeout); - - var seed = $('#seed').val(); - var codes = $('#memo').val(); - - addresses = []; - $('#chMsg').text(''); - $('#chain').text(''); - - Electrum.stop(); - - if (chain_type == 'chain_electrum') { - if (seed.length == 0) - return; - Electrum.init(seed, electrum_seed_update, electrum_seed_success); - } - - if (chain_type == 'chain_armory') { - var uid = Armory.gen(codes, chain_range, addr_callback, update_chain); - if (uid) - $('#chMsg').text('uid: ' + uid); - else - return; - } - } - // -- transactions -- var txType = 'txBCI'; var txFrom = 'txFromSec'; - function txGenSrcAddr() { - var sec = $('#txSec').val(); - var addr = ''; - - try { - var res = parseBase58Check(sec); - var version = res[0]; - var payload = res[1]; - var compressed = false; - if (payload.length > 32) { - payload.pop(); - compressed = true; - } - var eckey = new Bitcoin.ECKey(payload); - var curve = getSECCurveByName("secp256k1"); - var pt = curve.getG().multiply(eckey.priv); - eckey.pub = getEncoded(pt, compressed); - eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(eckey.pub); - addr = new Bitcoin.Address(eckey.getPubKeyHash()); - addr.version = (version-128)&255; - } catch (err) { - } - - $('#txAddr').val(addr); - $('#txBalance').val('0.00'); + function txOnChangeRedemptionScript() { + var bytes = Crypto.util.hexToBytes($('#txRedemptionScript').val()); + var redemption_script = new Bitcoin.Script(bytes); - if (addr != "" && txFrom=='txFromSec') - txGetUnspent(); - } + // Hash the script to produce the bitcoin address: + var redemptionScriptHash160 = Bitcoin.Util.sha256ripe160(redemption_script.buffer); + var p2sh_addr = new Bitcoin.Address(redemptionScriptHash160); + p2sh_addr.version = 5; + $("#txAddr").val('' + p2sh_addr); - function txOnChangeSec() { - clearTimeout(timeout); - timeout = setTimeout(txGenSrcAddr, TIMEOUT); - } + // Show/Hide private key spaces depending on M + var m = redemption_script.buffer[0] - Bitcoin.Opcode.map["OP_1"] + 1; + if( m < 1 || m > 3 ) { + setErrorState($('#txOnChangeRedemptionScript'), true, 'Redemption script is not valid'); + return; + } - function txOnChangeAddr() { - clearTimeout(timeout); - timeout = setTimeout(txGetUnspent, TIMEOUT); + $("#txSec1_group").removeClass('hidden'); + $("#txSec2_group").removeClass('hidden').addClass((m < 2) ? 'hidden' : ''); + $("#txSec3_group").removeClass('hidden').addClass((m < 3) ? 'hidden' : ''); } function txSetUnspent(text) { @@ -868,15 +545,14 @@ } function txSend() { - var txAddr = $('#txAddr').val(); - var address = TX.getAddress(); - var r = ''; - if (txAddr != address) - r += 'Warning! Source address does not match private key.\n\n'; - var tx = $('#txHex').val(); + // Disabled for now because Blockchain.info can't verify + // signatures on these transactions properly yet. + alert("Since Blockchain.info cannot correctly verify the signatures in a multi-signature transaction correctly yet, pushing is disabled. In order to broadcast this transaction, you need to use another service. Bitcoind/Bitcoin-Qt are known to work."); + return; + //url = 'http://bitsend.rowit.co.uk/?transaction=' + tx; url = 'http://blockchain.info/pushtx'; postdata = 'tx=' + tx; @@ -887,21 +563,14 @@ return false; } - function txRebuild() { - var sec = $('#txSec').val(); - var addr = $('#txAddr').val(); - var unspent = $('#txUnspent').val(); - var balance = parseFloat($('#txBalance').val()); - var fee = parseFloat('0'+$('#txFee').val()); - + function txKey(i) { + var sec = $('#txSec' + i).val(); try { var res = parseBase58Check(sec); var version = res[0]; var payload = res[1]; } catch (err) { - $('#txJSON').val(''); - $('#txHex').val(''); - return; + return null; } var compressed = false; @@ -911,10 +580,41 @@ } var eckey = new Bitcoin.ECKey(payload); - eckey.setCompressed(compressed); + return eckey; + } - TX.init(eckey); + function txRebuild() { + var bytes = Crypto.util.hexToBytes($('#txRedemptionScript').val()); + var redemption_script = new Bitcoin.Script(bytes); + var m = redemption_script.buffer[0] - Bitcoin.Opcode.map["OP_1"] + 1; + + var eckey1 = (m >= 1) ? txKey(1) : null; + var eckey2 = (m >= 2) ? txKey(2) : null; + var eckey3 = (m >= 3) ? txKey(3) : null; + + if( (m >= 3 && (eckey3 == null || eckey2 == null || eckey1 == null)) + || (m >= 2 && (eckey2 == null || eckey1 == null)) + || (m >= 1 && (eckey1 == null)) ) { + $('#txJSON').val(''); + $('#txHex').val(''); + return; + } + + var eckeys = new Array(); + if( m >= 1 ) + eckeys.push(eckey1); + if( m >= 2 ) + eckeys.push(eckey2); + if( m >= 3 ) + eckeys.push(eckey3); + + var addr = $('#txAddr').val(); + var unspent = $('#txUnspent').val(); + var balance = parseFloat($('#txBalance').val()); + var fee = parseFloat('0'+$('#txFee').val()); + + TX.init(eckeys, redemption_script); var fval = 0; var o = txGetOutputs(); @@ -924,10 +624,12 @@ } // send change back or it will be sent as fee - if (balance > fval + fee) { - var change = balance - fval - fee; - TX.addOutput(addr, change); - } + // Current BitcoinJS won't let us return the change to ourselves. + // It throwns an exception in Bitcoin.Address.decodeString complaining about "Version 5" addresses. + // if (balance > fval + fee) { + // var change = balance - fval - fee; + // TX.addOutput(addr, change); + // } try { var sendTx = TX.construct(); @@ -938,6 +640,9 @@ $('#txJSON').val(txJSON); $('#txHex').val(txHex); } catch(err) { + if( ('' + err) == 'Version 5 not supported!' ) { + alert("The current version of BitcoinJS does not support spending to P2SH addresses yet.") + } $('#txJSON').val(''); $('#txHex').val(''); } @@ -946,8 +651,8 @@ function txSign() { if (txFrom=='txFromSec') { - txRebuild(); - return; + txRebuild(); + return; } var str = $('#txJSON').val(); @@ -995,28 +700,6 @@ txType = $(this).attr('id'); } - function txChangeFrom() { - txFrom = $(this).attr('id'); - var bFromKey = txFrom=='txFromSec' || txFrom=='txFromPass'; - $('#txJSON').attr('readonly', txFrom!='txFromJSON'); - $('#txHex').attr('readonly', txFrom!='txFromRaw'); - $('#txFee').attr('readonly', !bFromKey); - $('#txAddr').attr('readonly', !bFromKey); - $('#txBalance').attr('readonly', !bFromKey); - - $.each($(document).find('.txCC'), function() { - $(this).find('#txDest').attr('readonly', !bFromKey); - $(this).find('#txValue').attr('readonly', !bFromKey); - }); - - if ( txFrom=='txFromRaw' ) - $('#txHex').focus(); - else if ( txFrom=='txFromJSON' ) - $('#txJSON').focus(); - else if ( bFromKey ) - $('#txSec').focus(); - } - function txOnChangeFee() { var balance = parseFloat($('#txBalance').val()); @@ -1052,112 +735,6 @@ return res; } - // -- sign -- - - function updateAddr(from, to) { - var sec = from.val(); - var addr = ''; - var eckey = null; - var compressed = false; - try { - var res = parseBase58Check(sec); - var version = res[0]; - var payload = res[1]; - if (payload.length > 32) { - payload.pop(); - compressed = true; - } - eckey = new Bitcoin.ECKey(payload); - var curve = getSECCurveByName("secp256k1"); - var pt = curve.getG().multiply(eckey.priv); - eckey.pub = getEncoded(pt, compressed); - eckey.pubKeyHash = Bitcoin.Util.sha256ripe160(eckey.pub); - addr = new Bitcoin.Address(eckey.getPubKeyHash()); - addr.version = (version-128)&255; - setErrorState(from, false); - } catch (err) { - setErrorState(from, true, "Bad private key"); - } - to.val(addr); - return {"key":eckey, "compressed":compressed, "addrtype":version}; - } - - function sgGenAddr() { - updateAddr($('#sgSec'), $('#sgAddr')); - } - - function sgOnChangeSec() { - $('#sgSig').val(''); - clearTimeout(timeout); - timeout = setTimeout(sgGenAddr, TIMEOUT); - } - - function sgSign() { - var message = $('#sgMsg').val(); - var p = updateAddr($('#sgSec'), $('#sgAddr')); - var sig = sign_message(p.key, message, p.compressed, p.addrtype); - $('#sgSig').val(sig); - } - - function sgOnChangeMsg() { - $('#sgSig').val(''); - clearTimeout(timeout); - timeout = setTimeout(sgUpdateMsg, TIMEOUT); - } - - function sgUpdateMsg() { - $('#vrMsg').val($('#sgMsg').val()); - } - - // -- verify -- - - function vrClearRes() { - $('#vrRes').text(''); - $('.vrMsg').removeClass('has-error'); - $('.vrSig').removeClass('has-error'); - $('.vrMsg').removeClass('has-success'); - $('.vrSig').removeClass('has-success'); - window.location.hash='#verify'; - } - - function vrPermalink() - { - var msg = $('#vrMsg').val(); - var sig = $('#vrSig').val(); - return '?vrMsg='+encodeURIComponent(msg)+'&vrSig='+encodeURIComponent(sig); - } - - function vrVerify() { - var msg = $('#vrMsg').val(); - var sig = $('#vrSig').val(); - var res = verify_message(sig, msg, PUBLIC_KEY_VERSION); - - if ( !msg ) - { - $('.vrMsg').addClass('has-error'); - return; - } - - if ( !sig ) - { - $('.vrSig').addClass('has-error'); - return; - } - - if (res) { - $('.vrMsg').removeClass('has-error'); - $('.vrSig').removeClass('has-error'); - var href = ADDRESS_URL_PREFIX+res; - var a = '' + res + ''; - $('#vrRes').html('verified to: ' + a); - } else { - $('#vrRes').text('not verified'); - } - - window.location.hash='#verify'+vrPermalink(); - return false; - } - function crChange() { PUBLIC_KEY_VERSION = parseInt($(this).attr('title')); @@ -1165,7 +742,6 @@ ADDRESS_URL_PREFIX = $(this).attr('href'); $('#crName').text($(this).text()); $('#crSelect').dropdown('toggle'); - gen_update(); translate(); return false; } @@ -1181,44 +757,34 @@ // generator - onInput('#pass', onChangePass); - onInput('#hash', onChangeHash); - onInput('#sec', onChangePrivKey); - - $('#genRandom').click(genRandom); - - $('#gen_from label input').on('change', update_gen_from ); - $('#gen_comp label input').on('change', update_gen_compressed ); + onInput('#pub1', onChangePublicKey); + onInput('#pub2', onChangePublicKey); + onInput('#pub3', onChangePublicKey); - genRandomPass(); - genUpdateLabel(); + $('#req_count label input').on('change', update_req_count ); + $('#outof_count label input').on('change', update_outof_count ); - // chains + initializePublicKeys(); + reqUpdateLabel(); + outofUpdateLabel(); - $('#chPlay').click(chOnPlay); - - $('#chain_from label input').on('change', onChangeMethod ); - $('#chain_format label input').on('change', onChangeFormat ); + // transactions - onInput($('#range'), onChangeRange); - onInput($('#seed'), onChangeSeed); - onInput($('#memo'), onChangeMemo); - onInput($('#elChange'), onChangeRange); + $("#txRedemptionScript").val('524104520ee4e1784521633bce92245367948081e1b81e59a19b2fefbe93d47c29e042000b16ea4d826a62e4eb30790c556f298870d0255f33aba91092123b11f961c8410498d2ffaba0eec39a2c403939fe5c0969271b279940720fc69e10e0664ba66f709110a5816dad3e378caec62f45c58f7226729749d0b4413cf47c5ffe3c89a79f4104d4b2d7638724d660671b9b8c3fc728f2abe486168c3bc7df868909d1d8e6f168162848c0777c3471c0ad7bb075c284445bf27c96131b0ca642b30cbf5f862cf653ae'); + txOnChangeRedemptionScript(); - // transactions + $("#txSec1").val('5KNVGaQirviYX8qKSwCePBvYaxDGxtudTwggCdFieCEHGLnDvje'); + $("#txSec2").val('5Jij3wX4T4BxKvJeAWq8mmfgoSPNCR6fMhMCFuXpibE4ECCxsiU'); + $("#txSec3").val('5K2GJtSnxu9xGpPTQU7X1F1HZCeHuFvBA5PncS79kLz1SsQUqQp'); - $('#txSec').val(tx_sec); - $('#txAddr').val(tx_addr); $('#txDest').val(tx_dest); txSetUnspent(tx_unspent); $('#txGetUnspent').click(txGetUnspent); $('#txType label input').on('change', txChangeType); - $('#txFrom label input').on('change', txChangeFrom); - onInput($('#txSec'), txOnChangeSec); - onInput($('#txAddr'), txOnChangeAddr); + onInput($('#txRedemptionScript'), txOnChangeRedemptionScript); onInput($('#txUnspent'), txOnChangeUnspent); onInput($('#txHex'), txOnChangeHex); onInput($('#txJSON'), txOnChangeJSON); @@ -1238,45 +804,6 @@ $('#enc_from label input').on('change', update_enc_from ); $('#enc_to label input').on('change', update_enc_to ); - // sign - - $('#sgSec').val('5JeWZ1z6sRcLTJXdQEDdB986E6XfLAkj9CgNE4EHzr5GmjrVFpf'); - $('#sgAddr').val('17mDAmveV5wBwxajBsY7g1trbMW1DVWcgL'); - $('#sgMsg').val("C'est par mon ordre et pour le bien de l'Etat que le porteur du présent a fait ce qu'il a fait."); - - onInput('#sgSec', sgOnChangeSec); - onInput('#sgMsg', sgOnChangeMsg); - - $('#sgSign').click(sgSign); - $('#sgForm').submit(sgSign); - - // verify - - var vrMsg = ''; - var vrSig = ''; - if ( window.location.hash && window.location.hash.indexOf('?')!=-1 ) - { - var args = window.location.hash.split('?')[1].split('&'); - for ( var i=0; i Date: Sun, 10 Nov 2013 20:48:47 +0700 Subject: [PATCH 02/21] Change to default compressed keys. --- README | 3 ++- index.html | 8 ++++---- js/brainwallet.js | 21 ++++++++++++++------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/README b/README index de5cab1..720dbf9 100644 --- a/README +++ b/README @@ -1,6 +1,6 @@ JavaScript Client-Side Multisignature P2SH Address and Transaction Generator -http://github.com/sarchar/brainwallet.github.com +http://github.com/ms-brainwallet/ms-brainwallet.github.io Notable features: @@ -10,3 +10,4 @@ Notable features: - OpenSSL point conversion and compressed keys support - RFC 1751 JavaScript implementation - Bitcoin transactions editor + diff --git a/index.html b/index.html index 7418cbd..0d62233 100644 --- a/index.html +++ b/index.html @@ -73,7 +73,7 @@
- +
@@ -86,7 +86,7 @@
- +
@@ -94,7 +94,7 @@
- +
@@ -102,7 +102,7 @@
- +
diff --git a/js/brainwallet.js b/js/brainwallet.js index 7e27f40..e5052a9 100644 --- a/js/brainwallet.js +++ b/js/brainwallet.js @@ -159,6 +159,10 @@ } function update_outof_count() { + // TODO - this must remain '3' for now, as only M-of-3 multisigs are considered standard right now. + $("#outof_3").click(); + return; + outof_count = parseInt($(this).attr('id').substring(6)) outofUpdateLabel(); update_outof(); @@ -229,9 +233,12 @@ } function initializePublicKeys() { - $('#pub1').val('045b18a4e0153f0b4a8864976d0c3e55ff6845857f8ee818ca5aa22fbdcfaf51f408005b719d7226310d191f993960055681de48a12ff430b7ca2f6298fbae19d5'); - $('#pub2').val('04edb36cd12605e32115916817d064733513992abcb69c12c2a3fe8a349d1bfe52f164e801f3d16b6dc5caaf8a901490a0a5c30ccde5a2e19b7f8a345d968ebf53'); - $('#pub3').val('04757f731c485c1e387a110a2441965c93c699d03aecaa9447154acbc0c28c23044dca44dbc63304957ed36ca0a16b800a31cd2ca8732ca8a7c5709c646c538bcc'); + // TODO - support/use compressed pubkeys and make them default + // TODO - BIP32 and chain-generation (pubkeys can be incremented). provide 3 seeds, increment based on index + // TODO - "meta" redeemscript (contains the keys) and can be used to seed BIP32 chains + $('#pub1').val('03d728ad6757d4784effea04d47baafa216cf474866c2d4dc99b1e8e3eb936e730'); + $('#pub2').val('02d83bba35a8022c247b645eed6f81ac41b7c1580de550e7e82c75ad63ee9ac2fd'); + $('#pub3').val('03aeb681df5ac19e449a872b9e9347f1db5a0394d2ec5caf2a9c143f86e232b0d9'); $('#pub1').focus(); reqUpdateLabel(); generate_redemption_script(); @@ -770,12 +777,12 @@ // transactions - $("#txRedemptionScript").val('524104520ee4e1784521633bce92245367948081e1b81e59a19b2fefbe93d47c29e042000b16ea4d826a62e4eb30790c556f298870d0255f33aba91092123b11f961c8410498d2ffaba0eec39a2c403939fe5c0969271b279940720fc69e10e0664ba66f709110a5816dad3e378caec62f45c58f7226729749d0b4413cf47c5ffe3c89a79f4104d4b2d7638724d660671b9b8c3fc728f2abe486168c3bc7df868909d1d8e6f168162848c0777c3471c0ad7bb075c284445bf27c96131b0ca642b30cbf5f862cf653ae'); + $("#txRedemptionScript").val('522103d728ad6757d4784effea04d47baafa216cf474866c2d4dc99b1e8e3eb936e7302102d83bba35a8022c247b645eed6f81ac41b7c1580de550e7e82c75ad63ee9ac2fd2103aeb681df5ac19e449a872b9e9347f1db5a0394d2ec5caf2a9c143f86e232b0d953ae'); txOnChangeRedemptionScript(); - $("#txSec1").val('5KNVGaQirviYX8qKSwCePBvYaxDGxtudTwggCdFieCEHGLnDvje'); - $("#txSec2").val('5Jij3wX4T4BxKvJeAWq8mmfgoSPNCR6fMhMCFuXpibE4ECCxsiU'); - $("#txSec3").val('5K2GJtSnxu9xGpPTQU7X1F1HZCeHuFvBA5PncS79kLz1SsQUqQp'); + $("#txSec1").val('KybuecAGpGhfLP4y6bd6bidFn23dGK2EJJi8zvbwjoffYd14EsU6'); + $("#txSec2").val('L11z9LhtCJmPPtK4cwMC4s9s9R3uXkuPkmGfjBmUGGHn7eFejiPC'); + $("#txSec3").val('L1idoWSvtirHZgYU5eVFGHSHG9xXB72AyLSupfQrs6JUvUAPSKzS'); $('#txDest').val(tx_dest); From cb9523a748c667a663b26ff8645792fedc24bf56 Mon Sep 17 00:00:00 2001 From: Chuck Date: Sun, 10 Nov 2013 21:21:58 +0700 Subject: [PATCH 03/21] Support extracting public keys from the redemption script. --- index.html | 15 ++++++++--- js/brainwallet.js | 65 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 0d62233..b5aaa55 100644 --- a/index.html +++ b/index.html @@ -62,6 +62,15 @@
Create Multisignature P2SH +
+ +
+
+ + +
    +
+
@@ -86,7 +95,7 @@
- +
@@ -94,7 +103,7 @@
- +
@@ -102,7 +111,7 @@
- +
diff --git a/js/brainwallet.js b/js/brainwallet.js index e5052a9..9308f4f 100644 --- a/js/brainwallet.js +++ b/js/brainwallet.js @@ -1,5 +1,6 @@ (function($){ + var pubkeys_from = 'manual'; var req_count = 2; var outof_count = 3; var gen_compressed = false; @@ -137,6 +138,25 @@ } } + function pubkeysFromUpdateLabel() { + $('#pubkeysFromMsg').text($('#pubkeys_from_'+pubkeys_from).parent().attr('title')); + } + + function updatePubkeysFrom() { + $('#pub1').attr('readonly', pubkeys_from != 'manual'); + $('#pub2').attr('readonly', pubkeys_from != 'manual'); + $('#pub3').attr('readonly', pubkeys_from != 'manual'); + $('#redemption_script').attr('readonly', pubkeys_from != 'redemption_script'); + if( pubkeys_from == 'manual' ) $("#pub1").focus(); + else if( pubkeys_from == 'redemption_script' ) $("#redemption_script").focus(); + } + + function update_pubkeys_from() { + pubkeys_from = $(this).attr('id').substring(13); + pubkeysFromUpdateLabel(); + updatePubkeysFrom(); + } + function reqUpdateLabel() { $('#reqMsg').text($('#req_'+req_count).parent().attr('title')); } @@ -210,8 +230,12 @@ var redemption_script_str = Crypto.util.bytesToHex(redemption_script.buffer); $("#redemption_script").val(redemption_script_str); + update_p2sh_address(); + } + + function update_p2sh_address() { // Hash the script to produce the bitcoin address: - var redemptionScriptHash160 = Bitcoin.Util.sha256ripe160(redemption_script.buffer); + var redemptionScriptHash160 = Bitcoin.Util.sha256ripe160(Crypto.util.hexToBytes($("#redemption_script").val())); var p2sh_addr = new Bitcoin.Address(redemptionScriptHash160); p2sh_addr.version = 5; $("#addr").val('' + p2sh_addr); @@ -227,11 +251,47 @@ $('#genAddrURL').attr('title', addr); } + function parse_redemption_script() { + var redemption_script_bytes = Crypto.util.hexToBytes($("#redemption_script").val()); + var redemption_script = new Bitcoin.Script(redemption_script_bytes); + + console.log(redemption_script.chunks); + var m = redemption_script.chunks[0] - Bitcoin.Opcode.map["OP_1"] + 1; + if( m < 1 || m > 3 ) return; + + var slen = redemption_script.chunks.length; + if( slen < 3 ) return; + if( redemption_script.chunks[slen - 1] != Bitcoin.Opcode.map["OP_CHECKMULTISIG"] ) return; + + var n = redemption_script.chunks[slen - 2] - Bitcoin.Opcode.map["OP_1"] + 1; + if( n < 1 || n > 3 ) return; + + // Temporary + if( n != 3 ) { + alert("Only m-of-3 multisignature transactions are supported."); + return; + } + + if( slen == (n + 3) ) { + if( n >= 1 ) $("#pub1").val(Crypto.util.bytesToHex(redemption_script.chunks[1])); + if( n >= 2 ) $("#pub2").val(Crypto.util.bytesToHex(redemption_script.chunks[2])); + if( n >= 3 ) $("#pub3").val(Crypto.util.bytesToHex(redemption_script.chunks[3])); + $("#req_" + m).click(); + $("#outof_" + n).click(); + update_p2sh_address(); + } + } + function onChangePublicKey() { clearTimeout(timeout); timeout = setTimeout(generate_redemption_script, TIMEOUT); } + function onChangeRedemptionScript() { + clearTimeout(timeout); + timeout = setTimeout(parse_redemption_script, TIMEOUT); + } + function initializePublicKeys() { // TODO - support/use compressed pubkeys and make them default // TODO - BIP32 and chain-generation (pubkeys can be incremented). provide 3 seeds, increment based on index @@ -240,6 +300,7 @@ $('#pub2').val('02d83bba35a8022c247b645eed6f81ac41b7c1580de550e7e82c75ad63ee9ac2fd'); $('#pub3').val('03aeb681df5ac19e449a872b9e9347f1db5a0394d2ec5caf2a9c143f86e232b0d9'); $('#pub1').focus(); + pubkeysFromUpdateLabel(); reqUpdateLabel(); generate_redemption_script(); } @@ -767,7 +828,9 @@ onInput('#pub1', onChangePublicKey); onInput('#pub2', onChangePublicKey); onInput('#pub3', onChangePublicKey); + onInput('#redemption_script', onChangeRedemptionScript); + $('#pubkeys_from label input').on('change', update_pubkeys_from ); $('#req_count label input').on('change', update_req_count ); $('#outof_count label input').on('change', update_outof_count ); From 517a5968400908d0a351c9b1b880401d6fc74d76 Mon Sep 17 00:00:00 2001 From: Chuck Date: Sun, 10 Nov 2013 21:25:44 +0700 Subject: [PATCH 04/21] Not needed --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index ab511a8..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -brainwallet.org \ No newline at end of file From f42108494f0432d931cfe6e61282a626280f096d Mon Sep 17 00:00:00 2001 From: Chuck Date: Thu, 14 Nov 2013 00:36:57 +0700 Subject: [PATCH 05/21] Implementing BIP32 for multisignature P2SH --- index.html | 151 ++++++++++++++++++++ js/bip32.js | 296 ++++++++++++++++++++++++++++++++++++++ js/brainwallet.js | 356 +++++++++++++++++++++++++++++++++++++++++++++- js/modsqrt.js | 139 ++++++++++++++++++ js/sha512.js | 32 +++++ 5 files changed, 969 insertions(+), 5 deletions(-) create mode 100644 js/bip32.js create mode 100644 js/modsqrt.js create mode 100644 js/sha512.js diff --git a/index.html b/index.html index b5aaa55..4167b33 100644 --- a/index.html +++ b/index.html @@ -13,10 +13,13 @@ + + + @@ -30,6 +33,7 @@