>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w<
15 | l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
16 | (function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])},
17 | _doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]),
18 | f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f,
19 | m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m,
20 | E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/
21 | 4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math);
22 | (function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,
28 | this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684,
29 | 1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})},
30 | decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d,
31 | b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}();
32 | (function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8,
33 | 16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;d d||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>>
34 | 8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t=
35 | d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})();
--------------------------------------------------------------------------------
/data/js/libs/promise.min.js:
--------------------------------------------------------------------------------
1 | !function(){var a,b,c,d;!function(){var e={},f={};a=function(a,b,c){e[a]={deps:b,callback:c}},d=c=b=function(a){function c(b){if("."!==b.charAt(0))return b;for(var c=b.split("/"),d=a.split("/").slice(0,-1),e=0,f=c.length;f>e;e++){var g=c[e];if(".."===g)d.pop();else{if("."===g)continue;d.push(g)}}return d.join("/")}if(d._eak_seen=e,f[a])return f[a];if(f[a]={},!e[a])throw new Error("Could not find module "+a);for(var g,h=e[a],i=h.deps,j=h.callback,k=[],l=0,m=i.length;m>l;l++)"exports"===i[l]?k.push(g={}):k.push(b(c(i[l])));var n=j.apply(this,k);return f[a]=g||n}}(),a("promise/all",["./utils","exports"],function(a,b){"use strict";function c(a){var b=this;if(!d(a))throw new TypeError("You must pass an array to all.");return new b(function(b,c){function d(a){return function(b){f(a,b)}}function f(a,c){h[a]=c,0===--i&&b(h)}var g,h=[],i=a.length;0===i&&b([]);for(var j=0;j= 7) {
76 | this.setupTypeNumber(test);
77 | }
78 |
79 | if (this.dataCache == null) {
80 | this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList);
81 | }
82 |
83 | this.mapData(this.dataCache, maskPattern);
84 | },
85 |
86 | setupPositionProbePattern: function (row, col) {
87 |
88 | for (var r = -1; r <= 7; r++) {
89 |
90 | if (row + r <= -1 || this.moduleCount <= row + r) continue;
91 |
92 | for (var c = -1; c <= 7; c++) {
93 |
94 | if (col + c <= -1 || this.moduleCount <= col + c) continue;
95 |
96 | if ((0 <= r && r <= 6 && (c == 0 || c == 6))
97 | || (0 <= c && c <= 6 && (r == 0 || r == 6))
98 | || (2 <= r && r <= 4 && 2 <= c && c <= 4)) {
99 | this.modules[row + r][col + c] = true;
100 | } else {
101 | this.modules[row + r][col + c] = false;
102 | }
103 | }
104 | }
105 | },
106 |
107 | getBestMaskPattern: function () {
108 |
109 | var minLostPoint = 0;
110 | var pattern = 0;
111 |
112 | for (var i = 0; i < 8; i++) {
113 |
114 | this.makeImpl(true, i);
115 |
116 | var lostPoint = QRCode.Util.getLostPoint(this);
117 |
118 | if (i == 0 || minLostPoint > lostPoint) {
119 | minLostPoint = lostPoint;
120 | pattern = i;
121 | }
122 | }
123 |
124 | return pattern;
125 | },
126 |
127 | createMovieClip: function (target_mc, instance_name, depth) {
128 |
129 | var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth);
130 | var cs = 1;
131 |
132 | this.make();
133 |
134 | for (var row = 0; row < this.modules.length; row++) {
135 |
136 | var y = row * cs;
137 |
138 | for (var col = 0; col < this.modules[row].length; col++) {
139 |
140 | var x = col * cs;
141 | var dark = this.modules[row][col];
142 |
143 | if (dark) {
144 | qr_mc.beginFill(0, 100);
145 | qr_mc.moveTo(x, y);
146 | qr_mc.lineTo(x + cs, y);
147 | qr_mc.lineTo(x + cs, y + cs);
148 | qr_mc.lineTo(x, y + cs);
149 | qr_mc.endFill();
150 | }
151 | }
152 | }
153 |
154 | return qr_mc;
155 | },
156 |
157 | setupTimingPattern: function () {
158 |
159 | for (var r = 8; r < this.moduleCount - 8; r++) {
160 | if (this.modules[r][6] != null) {
161 | continue;
162 | }
163 | this.modules[r][6] = (r % 2 == 0);
164 | }
165 |
166 | for (var c = 8; c < this.moduleCount - 8; c++) {
167 | if (this.modules[6][c] != null) {
168 | continue;
169 | }
170 | this.modules[6][c] = (c % 2 == 0);
171 | }
172 | },
173 |
174 | setupPositionAdjustPattern: function () {
175 |
176 | var pos = QRCode.Util.getPatternPosition(this.typeNumber);
177 |
178 | for (var i = 0; i < pos.length; i++) {
179 |
180 | for (var j = 0; j < pos.length; j++) {
181 |
182 | var row = pos[i];
183 | var col = pos[j];
184 |
185 | if (this.modules[row][col] != null) {
186 | continue;
187 | }
188 |
189 | for (var r = -2; r <= 2; r++) {
190 |
191 | for (var c = -2; c <= 2; c++) {
192 |
193 | if (r == -2 || r == 2 || c == -2 || c == 2
194 | || (r == 0 && c == 0)) {
195 | this.modules[row + r][col + c] = true;
196 | } else {
197 | this.modules[row + r][col + c] = false;
198 | }
199 | }
200 | }
201 | }
202 | }
203 | },
204 |
205 | setupTypeNumber: function (test) {
206 |
207 | var bits = QRCode.Util.getBCHTypeNumber(this.typeNumber);
208 |
209 | for (var i = 0; i < 18; i++) {
210 | var mod = (!test && ((bits >> i) & 1) == 1);
211 | this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
212 | }
213 |
214 | for (var i = 0; i < 18; i++) {
215 | var mod = (!test && ((bits >> i) & 1) == 1);
216 | this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
217 | }
218 | },
219 |
220 | setupTypeInfo: function (test, maskPattern) {
221 |
222 | var data = (this.errorCorrectLevel << 3) | maskPattern;
223 | var bits = QRCode.Util.getBCHTypeInfo(data);
224 |
225 | // vertical
226 | for (var i = 0; i < 15; i++) {
227 |
228 | var mod = (!test && ((bits >> i) & 1) == 1);
229 |
230 | if (i < 6) {
231 | this.modules[i][8] = mod;
232 | } else if (i < 8) {
233 | this.modules[i + 1][8] = mod;
234 | } else {
235 | this.modules[this.moduleCount - 15 + i][8] = mod;
236 | }
237 | }
238 |
239 | // horizontal
240 | for (var i = 0; i < 15; i++) {
241 |
242 | var mod = (!test && ((bits >> i) & 1) == 1);
243 |
244 | if (i < 8) {
245 | this.modules[8][this.moduleCount - i - 1] = mod;
246 | } else if (i < 9) {
247 | this.modules[8][15 - i - 1 + 1] = mod;
248 | } else {
249 | this.modules[8][15 - i - 1] = mod;
250 | }
251 | }
252 |
253 | // fixed module
254 | this.modules[this.moduleCount - 8][8] = (!test);
255 |
256 | },
257 |
258 | mapData: function (data, maskPattern) {
259 |
260 | var inc = -1;
261 | var row = this.moduleCount - 1;
262 | var bitIndex = 7;
263 | var byteIndex = 0;
264 |
265 | for (var col = this.moduleCount - 1; col > 0; col -= 2) {
266 |
267 | if (col == 6) col--;
268 |
269 | while (true) {
270 |
271 | for (var c = 0; c < 2; c++) {
272 |
273 | if (this.modules[row][col - c] == null) {
274 |
275 | var dark = false;
276 |
277 | if (byteIndex < data.length) {
278 | dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
279 | }
280 |
281 | var mask = QRCode.Util.getMask(maskPattern, row, col - c);
282 |
283 | if (mask) {
284 | dark = !dark;
285 | }
286 |
287 | this.modules[row][col - c] = dark;
288 | bitIndex--;
289 |
290 | if (bitIndex == -1) {
291 | byteIndex++;
292 | bitIndex = 7;
293 | }
294 | }
295 | }
296 |
297 | row += inc;
298 |
299 | if (row < 0 || this.moduleCount <= row) {
300 | row -= inc;
301 | inc = -inc;
302 | break;
303 | }
304 | }
305 | }
306 |
307 | }
308 |
309 | };
310 |
311 | QRCode.PAD0 = 0xEC;
312 | QRCode.PAD1 = 0x11;
313 |
314 | QRCode.createData = function (typeNumber, errorCorrectLevel, dataList) {
315 |
316 | var rsBlocks = QRCode.RSBlock.getRSBlocks(typeNumber, errorCorrectLevel);
317 |
318 | var buffer = new QRCode.BitBuffer();
319 |
320 | for (var i = 0; i < dataList.length; i++) {
321 | var data = dataList[i];
322 | buffer.put(data.mode, 4);
323 | buffer.put(data.getLength(), QRCode.Util.getLengthInBits(data.mode, typeNumber));
324 | data.write(buffer);
325 | }
326 |
327 | // calc num max data.
328 | var totalDataCount = 0;
329 | for (var i = 0; i < rsBlocks.length; i++) {
330 | totalDataCount += rsBlocks[i].dataCount;
331 | }
332 |
333 | if (buffer.getLengthInBits() > totalDataCount * 8) {
334 | throw new Error("code length overflow. ("
335 | + buffer.getLengthInBits()
336 | + ">"
337 | + totalDataCount * 8
338 | + ")");
339 | }
340 |
341 | // end code
342 | if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
343 | buffer.put(0, 4);
344 | }
345 |
346 | // padding
347 | while (buffer.getLengthInBits() % 8 != 0) {
348 | buffer.putBit(false);
349 | }
350 |
351 | // padding
352 | while (true) {
353 |
354 | if (buffer.getLengthInBits() >= totalDataCount * 8) {
355 | break;
356 | }
357 | buffer.put(QRCode.PAD0, 8);
358 |
359 | if (buffer.getLengthInBits() >= totalDataCount * 8) {
360 | break;
361 | }
362 | buffer.put(QRCode.PAD1, 8);
363 | }
364 |
365 | return QRCode.createBytes(buffer, rsBlocks);
366 | };
367 |
368 | QRCode.createBytes = function (buffer, rsBlocks) {
369 |
370 | var offset = 0;
371 |
372 | var maxDcCount = 0;
373 | var maxEcCount = 0;
374 |
375 | var dcdata = new Array(rsBlocks.length);
376 | var ecdata = new Array(rsBlocks.length);
377 |
378 | for (var r = 0; r < rsBlocks.length; r++) {
379 |
380 | var dcCount = rsBlocks[r].dataCount;
381 | var ecCount = rsBlocks[r].totalCount - dcCount;
382 |
383 | maxDcCount = Math.max(maxDcCount, dcCount);
384 | maxEcCount = Math.max(maxEcCount, ecCount);
385 |
386 | dcdata[r] = new Array(dcCount);
387 |
388 | for (var i = 0; i < dcdata[r].length; i++) {
389 | dcdata[r][i] = 0xff & buffer.buffer[i + offset];
390 | }
391 | offset += dcCount;
392 |
393 | var rsPoly = QRCode.Util.getErrorCorrectPolynomial(ecCount);
394 | var rawPoly = new QRCode.Polynomial(dcdata[r], rsPoly.getLength() - 1);
395 |
396 | var modPoly = rawPoly.mod(rsPoly);
397 | ecdata[r] = new Array(rsPoly.getLength() - 1);
398 | for (var i = 0; i < ecdata[r].length; i++) {
399 | var modIndex = i + modPoly.getLength() - ecdata[r].length;
400 | ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0;
401 | }
402 |
403 | }
404 |
405 | var totalCodeCount = 0;
406 | for (var i = 0; i < rsBlocks.length; i++) {
407 | totalCodeCount += rsBlocks[i].totalCount;
408 | }
409 |
410 | var data = new Array(totalCodeCount);
411 | var index = 0;
412 |
413 | for (var i = 0; i < maxDcCount; i++) {
414 | for (var r = 0; r < rsBlocks.length; r++) {
415 | if (i < dcdata[r].length) {
416 | data[index++] = dcdata[r][i];
417 | }
418 | }
419 | }
420 |
421 | for (var i = 0; i < maxEcCount; i++) {
422 | for (var r = 0; r < rsBlocks.length; r++) {
423 | if (i < ecdata[r].length) {
424 | data[index++] = ecdata[r][i];
425 | }
426 | }
427 | }
428 |
429 | return data;
430 |
431 | };
432 |
433 | //---------------------------------------------------------------------
434 | // QR8bitByte
435 | //---------------------------------------------------------------------
436 | QRCode.QR8bitByte = function (data) {
437 | this.mode = QRCode.Mode.MODE_8BIT_BYTE;
438 | this.data = data;
439 | }
440 |
441 | QRCode.QR8bitByte.prototype = {
442 | getLength: function (buffer) {
443 | return this.data.length;
444 | },
445 |
446 | write: function (buffer) {
447 | for (var i = 0; i < this.data.length; i++) {
448 | // not JIS ...
449 | buffer.put(this.data.charCodeAt(i), 8);
450 | }
451 | }
452 | };
453 |
454 |
455 | //---------------------------------------------------------------------
456 | // QRMode
457 | //---------------------------------------------------------------------
458 | QRCode.Mode = {
459 | MODE_NUMBER: 1 << 0,
460 | MODE_ALPHA_NUM: 1 << 1,
461 | MODE_8BIT_BYTE: 1 << 2,
462 | MODE_KANJI: 1 << 3
463 | };
464 |
465 | //---------------------------------------------------------------------
466 | // QRErrorCorrectLevel
467 | //---------------------------------------------------------------------
468 | QRCode.ErrorCorrectLevel = {
469 | L: 1,
470 | M: 0,
471 | Q: 3,
472 | H: 2
473 | };
474 |
475 |
476 | //---------------------------------------------------------------------
477 | // QRMaskPattern
478 | //---------------------------------------------------------------------
479 | QRCode.MaskPattern = {
480 | PATTERN000: 0,
481 | PATTERN001: 1,
482 | PATTERN010: 2,
483 | PATTERN011: 3,
484 | PATTERN100: 4,
485 | PATTERN101: 5,
486 | PATTERN110: 6,
487 | PATTERN111: 7
488 | };
489 |
490 | //---------------------------------------------------------------------
491 | // QRUtil
492 | //---------------------------------------------------------------------
493 |
494 | QRCode.Util = {
495 |
496 | PATTERN_POSITION_TABLE: [
497 | [],
498 | [6, 18],
499 | [6, 22],
500 | [6, 26],
501 | [6, 30],
502 | [6, 34],
503 | [6, 22, 38],
504 | [6, 24, 42],
505 | [6, 26, 46],
506 | [6, 28, 50],
507 | [6, 30, 54],
508 | [6, 32, 58],
509 | [6, 34, 62],
510 | [6, 26, 46, 66],
511 | [6, 26, 48, 70],
512 | [6, 26, 50, 74],
513 | [6, 30, 54, 78],
514 | [6, 30, 56, 82],
515 | [6, 30, 58, 86],
516 | [6, 34, 62, 90],
517 | [6, 28, 50, 72, 94],
518 | [6, 26, 50, 74, 98],
519 | [6, 30, 54, 78, 102],
520 | [6, 28, 54, 80, 106],
521 | [6, 32, 58, 84, 110],
522 | [6, 30, 58, 86, 114],
523 | [6, 34, 62, 90, 118],
524 | [6, 26, 50, 74, 98, 122],
525 | [6, 30, 54, 78, 102, 126],
526 | [6, 26, 52, 78, 104, 130],
527 | [6, 30, 56, 82, 108, 134],
528 | [6, 34, 60, 86, 112, 138],
529 | [6, 30, 58, 86, 114, 142],
530 | [6, 34, 62, 90, 118, 146],
531 | [6, 30, 54, 78, 102, 126, 150],
532 | [6, 24, 50, 76, 102, 128, 154],
533 | [6, 28, 54, 80, 106, 132, 158],
534 | [6, 32, 58, 84, 110, 136, 162],
535 | [6, 26, 54, 82, 110, 138, 166],
536 | [6, 30, 58, 86, 114, 142, 170]
537 | ],
538 |
539 | G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0),
540 | G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0),
541 | G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1),
542 |
543 | getBCHTypeInfo: function (data) {
544 | var d = data << 10;
545 | while (QRCode.Util.getBCHDigit(d) - QRCode.Util.getBCHDigit(QRCode.Util.G15) >= 0) {
546 | d ^= (QRCode.Util.G15 << (QRCode.Util.getBCHDigit(d) - QRCode.Util.getBCHDigit(QRCode.Util.G15)));
547 | }
548 | return ((data << 10) | d) ^ QRCode.Util.G15_MASK;
549 | },
550 |
551 | getBCHTypeNumber: function (data) {
552 | var d = data << 12;
553 | while (QRCode.Util.getBCHDigit(d) - QRCode.Util.getBCHDigit(QRCode.Util.G18) >= 0) {
554 | d ^= (QRCode.Util.G18 << (QRCode.Util.getBCHDigit(d) - QRCode.Util.getBCHDigit(QRCode.Util.G18)));
555 | }
556 | return (data << 12) | d;
557 | },
558 |
559 | getBCHDigit: function (data) {
560 |
561 | var digit = 0;
562 |
563 | while (data != 0) {
564 | digit++;
565 | data >>>= 1;
566 | }
567 |
568 | return digit;
569 | },
570 |
571 | getPatternPosition: function (typeNumber) {
572 | return QRCode.Util.PATTERN_POSITION_TABLE[typeNumber - 1];
573 | },
574 |
575 | getMask: function (maskPattern, i, j) {
576 |
577 | switch (maskPattern) {
578 |
579 | case QRCode.MaskPattern.PATTERN000: return (i + j) % 2 == 0;
580 | case QRCode.MaskPattern.PATTERN001: return i % 2 == 0;
581 | case QRCode.MaskPattern.PATTERN010: return j % 3 == 0;
582 | case QRCode.MaskPattern.PATTERN011: return (i + j) % 3 == 0;
583 | case QRCode.MaskPattern.PATTERN100: return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
584 | case QRCode.MaskPattern.PATTERN101: return (i * j) % 2 + (i * j) % 3 == 0;
585 | case QRCode.MaskPattern.PATTERN110: return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
586 | case QRCode.MaskPattern.PATTERN111: return ((i * j) % 3 + (i + j) % 2) % 2 == 0;
587 |
588 | default:
589 | throw new Error("bad maskPattern:" + maskPattern);
590 | }
591 | },
592 |
593 | getErrorCorrectPolynomial: function (errorCorrectLength) {
594 |
595 | var a = new QRCode.Polynomial([1], 0);
596 |
597 | for (var i = 0; i < errorCorrectLength; i++) {
598 | a = a.multiply(new QRCode.Polynomial([1, QRCode.Math.gexp(i)], 0));
599 | }
600 |
601 | return a;
602 | },
603 |
604 | getLengthInBits: function (mode, type) {
605 |
606 | if (1 <= type && type < 10) {
607 |
608 | // 1 - 9
609 |
610 | switch (mode) {
611 | case QRCode.Mode.MODE_NUMBER: return 10;
612 | case QRCode.Mode.MODE_ALPHA_NUM: return 9;
613 | case QRCode.Mode.MODE_8BIT_BYTE: return 8;
614 | case QRCode.Mode.MODE_KANJI: return 8;
615 | default:
616 | throw new Error("mode:" + mode);
617 | }
618 |
619 | } else if (type < 27) {
620 |
621 | // 10 - 26
622 |
623 | switch (mode) {
624 | case QRCode.Mode.MODE_NUMBER: return 12;
625 | case QRCode.Mode.MODE_ALPHA_NUM: return 11;
626 | case QRCode.Mode.MODE_8BIT_BYTE: return 16;
627 | case QRCode.Mode.MODE_KANJI: return 10;
628 | default:
629 | throw new Error("mode:" + mode);
630 | }
631 |
632 | } else if (type < 41) {
633 |
634 | // 27 - 40
635 |
636 | switch (mode) {
637 | case QRCode.Mode.MODE_NUMBER: return 14;
638 | case QRCode.Mode.MODE_ALPHA_NUM: return 13;
639 | case QRCode.Mode.MODE_8BIT_BYTE: return 16;
640 | case QRCode.Mode.MODE_KANJI: return 12;
641 | default:
642 | throw new Error("mode:" + mode);
643 | }
644 |
645 | } else {
646 | throw new Error("type:" + type);
647 | }
648 | },
649 |
650 | getLostPoint: function (qrCode) {
651 |
652 | var moduleCount = qrCode.getModuleCount();
653 |
654 | var lostPoint = 0;
655 |
656 | // LEVEL1
657 |
658 | for (var row = 0; row < moduleCount; row++) {
659 |
660 | for (var col = 0; col < moduleCount; col++) {
661 |
662 | var sameCount = 0;
663 | var dark = qrCode.isDark(row, col);
664 |
665 | for (var r = -1; r <= 1; r++) {
666 |
667 | if (row + r < 0 || moduleCount <= row + r) {
668 | continue;
669 | }
670 |
671 | for (var c = -1; c <= 1; c++) {
672 |
673 | if (col + c < 0 || moduleCount <= col + c) {
674 | continue;
675 | }
676 |
677 | if (r == 0 && c == 0) {
678 | continue;
679 | }
680 |
681 | if (dark == qrCode.isDark(row + r, col + c)) {
682 | sameCount++;
683 | }
684 | }
685 | }
686 |
687 | if (sameCount > 5) {
688 | lostPoint += (3 + sameCount - 5);
689 | }
690 | }
691 | }
692 |
693 | // LEVEL2
694 |
695 | for (var row = 0; row < moduleCount - 1; row++) {
696 | for (var col = 0; col < moduleCount - 1; col++) {
697 | var count = 0;
698 | if (qrCode.isDark(row, col)) count++;
699 | if (qrCode.isDark(row + 1, col)) count++;
700 | if (qrCode.isDark(row, col + 1)) count++;
701 | if (qrCode.isDark(row + 1, col + 1)) count++;
702 | if (count == 0 || count == 4) {
703 | lostPoint += 3;
704 | }
705 | }
706 | }
707 |
708 | // LEVEL3
709 |
710 | for (var row = 0; row < moduleCount; row++) {
711 | for (var col = 0; col < moduleCount - 6; col++) {
712 | if (qrCode.isDark(row, col)
713 | && !qrCode.isDark(row, col + 1)
714 | && qrCode.isDark(row, col + 2)
715 | && qrCode.isDark(row, col + 3)
716 | && qrCode.isDark(row, col + 4)
717 | && !qrCode.isDark(row, col + 5)
718 | && qrCode.isDark(row, col + 6)) {
719 | lostPoint += 40;
720 | }
721 | }
722 | }
723 |
724 | for (var col = 0; col < moduleCount; col++) {
725 | for (var row = 0; row < moduleCount - 6; row++) {
726 | if (qrCode.isDark(row, col)
727 | && !qrCode.isDark(row + 1, col)
728 | && qrCode.isDark(row + 2, col)
729 | && qrCode.isDark(row + 3, col)
730 | && qrCode.isDark(row + 4, col)
731 | && !qrCode.isDark(row + 5, col)
732 | && qrCode.isDark(row + 6, col)) {
733 | lostPoint += 40;
734 | }
735 | }
736 | }
737 |
738 | // LEVEL4
739 |
740 | var darkCount = 0;
741 |
742 | for (var col = 0; col < moduleCount; col++) {
743 | for (var row = 0; row < moduleCount; row++) {
744 | if (qrCode.isDark(row, col)) {
745 | darkCount++;
746 | }
747 | }
748 | }
749 |
750 | var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
751 | lostPoint += ratio * 10;
752 |
753 | return lostPoint;
754 | }
755 |
756 | };
757 |
758 |
759 | //---------------------------------------------------------------------
760 | // QRMath
761 | //---------------------------------------------------------------------
762 |
763 | QRCode.Math = {
764 |
765 | glog: function (n) {
766 |
767 | if (n < 1) {
768 | throw new Error("glog(" + n + ")");
769 | }
770 |
771 | return QRCode.Math.LOG_TABLE[n];
772 | },
773 |
774 | gexp: function (n) {
775 |
776 | while (n < 0) {
777 | n += 255;
778 | }
779 |
780 | while (n >= 256) {
781 | n -= 255;
782 | }
783 |
784 | return QRCode.Math.EXP_TABLE[n];
785 | },
786 |
787 | EXP_TABLE: new Array(256),
788 |
789 | LOG_TABLE: new Array(256)
790 |
791 | };
792 |
793 | for (var i = 0; i < 8; i++) {
794 | QRCode.Math.EXP_TABLE[i] = 1 << i;
795 | }
796 | for (var i = 8; i < 256; i++) {
797 | QRCode.Math.EXP_TABLE[i] = QRCode.Math.EXP_TABLE[i - 4]
798 | ^ QRCode.Math.EXP_TABLE[i - 5]
799 | ^ QRCode.Math.EXP_TABLE[i - 6]
800 | ^ QRCode.Math.EXP_TABLE[i - 8];
801 | }
802 | for (var i = 0; i < 255; i++) {
803 | QRCode.Math.LOG_TABLE[QRCode.Math.EXP_TABLE[i]] = i;
804 | }
805 |
806 | //---------------------------------------------------------------------
807 | // QRPolynomial
808 | //---------------------------------------------------------------------
809 |
810 | QRCode.Polynomial = function (num, shift) {
811 |
812 | if (num.length == undefined) {
813 | throw new Error(num.length + "/" + shift);
814 | }
815 |
816 | var offset = 0;
817 |
818 | while (offset < num.length && num[offset] == 0) {
819 | offset++;
820 | }
821 |
822 | this.num = new Array(num.length - offset + shift);
823 | for (var i = 0; i < num.length - offset; i++) {
824 | this.num[i] = num[i + offset];
825 | }
826 | }
827 |
828 | QRCode.Polynomial.prototype = {
829 |
830 | get: function (index) {
831 | return this.num[index];
832 | },
833 |
834 | getLength: function () {
835 | return this.num.length;
836 | },
837 |
838 | multiply: function (e) {
839 |
840 | var num = new Array(this.getLength() + e.getLength() - 1);
841 |
842 | for (var i = 0; i < this.getLength(); i++) {
843 | for (var j = 0; j < e.getLength(); j++) {
844 | num[i + j] ^= QRCode.Math.gexp(QRCode.Math.glog(this.get(i)) + QRCode.Math.glog(e.get(j)));
845 | }
846 | }
847 |
848 | return new QRCode.Polynomial(num, 0);
849 | },
850 |
851 | mod: function (e) {
852 |
853 | if (this.getLength() - e.getLength() < 0) {
854 | return this;
855 | }
856 |
857 | var ratio = QRCode.Math.glog(this.get(0)) - QRCode.Math.glog(e.get(0));
858 |
859 | var num = new Array(this.getLength());
860 |
861 | for (var i = 0; i < this.getLength(); i++) {
862 | num[i] = this.get(i);
863 | }
864 |
865 | for (var i = 0; i < e.getLength(); i++) {
866 | num[i] ^= QRCode.Math.gexp(QRCode.Math.glog(e.get(i)) + ratio);
867 | }
868 |
869 | // recursive call
870 | return new QRCode.Polynomial(num, 0).mod(e);
871 | }
872 | };
873 |
874 | //---------------------------------------------------------------------
875 | // QRRSBlock
876 | //---------------------------------------------------------------------
877 |
878 | QRCode.RSBlock = function (totalCount, dataCount) {
879 | this.totalCount = totalCount;
880 | this.dataCount = dataCount;
881 | }
882 |
883 | QRCode.RSBlock.RS_BLOCK_TABLE = [
884 |
885 | // L
886 | // M
887 | // Q
888 | // H
889 |
890 | // 1
891 | [1, 26, 19],
892 | [1, 26, 16],
893 | [1, 26, 13],
894 | [1, 26, 9],
895 |
896 | // 2
897 | [1, 44, 34],
898 | [1, 44, 28],
899 | [1, 44, 22],
900 | [1, 44, 16],
901 |
902 | // 3
903 | [1, 70, 55],
904 | [1, 70, 44],
905 | [2, 35, 17],
906 | [2, 35, 13],
907 |
908 | // 4
909 | [1, 100, 80],
910 | [2, 50, 32],
911 | [2, 50, 24],
912 | [4, 25, 9],
913 |
914 | // 5
915 | [1, 134, 108],
916 | [2, 67, 43],
917 | [2, 33, 15, 2, 34, 16],
918 | [2, 33, 11, 2, 34, 12],
919 |
920 | // 6
921 | [2, 86, 68],
922 | [4, 43, 27],
923 | [4, 43, 19],
924 | [4, 43, 15],
925 |
926 | // 7
927 | [2, 98, 78],
928 | [4, 49, 31],
929 | [2, 32, 14, 4, 33, 15],
930 | [4, 39, 13, 1, 40, 14],
931 |
932 | // 8
933 | [2, 121, 97],
934 | [2, 60, 38, 2, 61, 39],
935 | [4, 40, 18, 2, 41, 19],
936 | [4, 40, 14, 2, 41, 15],
937 |
938 | // 9
939 | [2, 146, 116],
940 | [3, 58, 36, 2, 59, 37],
941 | [4, 36, 16, 4, 37, 17],
942 | [4, 36, 12, 4, 37, 13],
943 |
944 | // 10
945 | [2, 86, 68, 2, 87, 69],
946 | [4, 69, 43, 1, 70, 44],
947 | [6, 43, 19, 2, 44, 20],
948 | [6, 43, 15, 2, 44, 16]
949 |
950 | ];
951 |
952 | QRCode.RSBlock.getRSBlocks = function (typeNumber, errorCorrectLevel) {
953 |
954 | var rsBlock = QRCode.RSBlock.getRsBlockTable(typeNumber, errorCorrectLevel);
955 |
956 | if (rsBlock == undefined) {
957 | throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel);
958 | }
959 |
960 | var length = rsBlock.length / 3;
961 |
962 | var list = new Array();
963 |
964 | for (var i = 0; i < length; i++) {
965 |
966 | var count = rsBlock[i * 3 + 0];
967 | var totalCount = rsBlock[i * 3 + 1];
968 | var dataCount = rsBlock[i * 3 + 2];
969 |
970 | for (var j = 0; j < count; j++) {
971 | list.push(new QRCode.RSBlock(totalCount, dataCount));
972 | }
973 | }
974 |
975 | return list;
976 | };
977 |
978 | QRCode.RSBlock.getRsBlockTable = function (typeNumber, errorCorrectLevel) {
979 |
980 | switch (errorCorrectLevel) {
981 | case QRCode.ErrorCorrectLevel.L:
982 | return QRCode.RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
983 | case QRCode.ErrorCorrectLevel.M:
984 | return QRCode.RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
985 | case QRCode.ErrorCorrectLevel.Q:
986 | return QRCode.RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
987 | case QRCode.ErrorCorrectLevel.H:
988 | return QRCode.RSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
989 | default:
990 | return undefined;
991 | }
992 | };
993 |
994 | //---------------------------------------------------------------------
995 | // QRBitBuffer
996 | //---------------------------------------------------------------------
997 |
998 | QRCode.BitBuffer = function () {
999 | this.buffer = new Array();
1000 | this.length = 0;
1001 | }
1002 |
1003 | QRCode.BitBuffer.prototype = {
1004 |
1005 | get: function (index) {
1006 | var bufIndex = Math.floor(index / 8);
1007 | return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1) == 1;
1008 | },
1009 |
1010 | put: function (num, length) {
1011 | for (var i = 0; i < length; i++) {
1012 | this.putBit(((num >>> (length - i - 1)) & 1) == 1);
1013 | }
1014 | },
1015 |
1016 | getLengthInBits: function () {
1017 | return this.length;
1018 | },
1019 |
1020 | putBit: function (bit) {
1021 |
1022 | var bufIndex = Math.floor(this.length / 8);
1023 | if (this.buffer.length <= bufIndex) {
1024 | this.buffer.push(0);
1025 | }
1026 |
1027 | if (bit) {
1028 | this.buffer[bufIndex] |= (0x80 >>> (this.length % 8));
1029 | }
1030 |
1031 | this.length++;
1032 | }
1033 | };
1034 | })();
--------------------------------------------------------------------------------
/data/js/paypopup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * paypopup.js
3 | * Copyright (c) 2014 Andrew Toth
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the MIT license.
7 | *
8 | * Controls paypopup.html, the popup that appears when clicking on bitcoin pay links,
9 | * or by clicking the context menu
10 | */
11 |
12 | $(document).ready(function () {
13 | var SATOSHIS = 100000000,
14 | FEE = SATOSHIS * .0001,
15 | BTCUnits = 'BTC',
16 | BTCMultiplier = SATOSHIS,
17 | clickX,
18 | clickY,
19 | port = null;
20 |
21 | // Event is broadcast when context menu is opened on the page
22 | $(document).on('contextmenu', function (e) {
23 | // Save the position of the right click to use for positioning the popup
24 | clickX = e.clientX;
25 | clickY = e.clientY;
26 | if (typeof chrome !== 'undefined') {
27 | // In Chrome we open a port with the background script
28 | // to tell us when the menu item is clicked
29 | if (port) {
30 | port.disconnect();
31 | }
32 | port = chrome.runtime.connect();
33 | port.onMessage.addListener(function(response) {
34 | var rect = null;
35 | if (response.address) {
36 | // We only have an address in Chrome if it was selected by right clicking,
37 | // so we can get the location of the address by finding the selection
38 | rect = window.getSelection().getRangeAt(0).getBoundingClientRect();
39 | }
40 | showPopup(response.address, null, rect);
41 | });
42 | }
43 | });
44 |
45 | if (typeof chrome === 'undefined') {
46 | // In Firefox we listen for the pay message to be sent
47 | self.port.on('pay', function (message) {
48 | if (message.address) {
49 | // If we have an address, the position of the address is sent as well
50 | var rect = {};
51 | rect.left = message.left;
52 | rect.right = message.right;
53 | rect.top = message.top;
54 | rect.bottom = message.bottom;
55 | showPopup(message.address, null, rect);
56 | } else {
57 | showPopup(null, null, null);
58 | }
59 | });
60 | }
61 |
62 | // Intercept all anchor clicks and determine if they are bitcoin pay links
63 | $('body').on('click', 'a', function (e) {
64 | var href = $(this).attr('href');
65 | // Regex test for bitcoin pay link
66 | if (/^bitcoin:[13][1-9A-HJ-NP-Za-km-z]{26,33}/.test(href)) {
67 | var addresses = href.match(/[13][1-9A-HJ-NP-Za-km-z]{26,33}/);
68 | var address = null;
69 | if (addresses) {
70 | address = addresses[0];
71 | }
72 | var amounts = href.match(/amount=\d+\.?\d*/);
73 | var amount = null;
74 | if (amounts) {
75 | amount = Number(amounts[0].substring(7)) * SATOSHIS;
76 | }
77 | showPopup(address, amount, this.getBoundingClientRect());
78 | return false;
79 | }
80 | // Return true if not a bitcoin link so click will work normally
81 | return true;
82 | });
83 |
84 | function showPopup(address, amount, rect) {
85 | util.iframe('paypopup.html').then(function (iframe) {
86 |
87 | iframe.style.height = '210px';
88 | iframe.style.width = '210px';
89 | var offset = {}
90 | if (rect) {
91 | offset.left = Number(rect.left) + Number(window.pageXOffset) + Number(rect.right-rect.left)/2 - 85;
92 | offset.top = Number(rect.bottom) + Number(window.pageYOffset);
93 | } else {
94 | offset.left = Number(clickX) + Number(window.pageXOffset);
95 | offset.top = Number(clickY) + Number(window.pageYOffset);
96 | }
97 | iframe.style.left = offset.left + 'px';
98 | iframe.style.top = offset.top + 'px';
99 |
100 | var $iframe = $(iframe.contentWindow.document);
101 |
102 | wallet.restoreAddress().then(function () {
103 | if (wallet.isEncrypted()) {
104 | // Only show password field if the wallet is encrypted
105 | $iframe.find('#password').parent().show();
106 | }
107 | }, function () {
108 | wallet.generateAddress();
109 | });
110 |
111 | preferences.getBTCUnits().then(function (units) {
112 | BTCUnits = units;
113 | if (units === 'µBTC') {
114 | BTCMultiplier = SATOSHIS / 1000000;
115 | } else if (units === 'mBTC') {
116 | BTCMultiplier = SATOSHIS / 1000;
117 | } else {
118 | BTCMultiplier = SATOSHIS;
119 | }
120 | $iframe.find('#amount').attr('placeholder', 'Amount (' + BTCUnits + ')').attr('step', 100000 / BTCMultiplier);
121 | });
122 |
123 | // Check if the address is actually valid
124 | if (!address || !/^[13][1-9A-HJ-NP-Za-km-z]{26,33}$/.test(String(address))) {
125 | address = null;
126 | } else {
127 | try {
128 | new Bitcoin.Address(address);
129 | } catch (e) {
130 | address = null;
131 | }
132 | }
133 |
134 | // Hide the address field if we have a valid address,
135 | // else hide the arrow pointing to an address
136 | if (address) {
137 | $iframe.find('#address').val(address).parent().hide();
138 | } else {
139 | $iframe.find('.arrow').hide();
140 | }
141 |
142 | // Hide the amount field if we have a valid amount
143 | if (amount) {
144 | $iframe.find('#amount').parent().hide();
145 | updateButton(amount);
146 | } else {
147 | $iframe.find('#amount').on('keyup change', function () {
148 | var value = Math.floor(Number($iframe.find('#amount').val() * BTCMultiplier));
149 | updateButton(value);
150 | });
151 | }
152 |
153 | function updateButton(value) {
154 | currencyManager.formatAmount(value).then(function (formattedMoney) {
155 | var text = 'Send';
156 | if (value > 0) {
157 | text += ' (' + formattedMoney + ')';
158 | }
159 | $iframe.find('#button').text(text);
160 | });
161 | }
162 |
163 | $iframe.find('#main').fadeIn('fast');
164 |
165 | $iframe.find('#button').click(function () {
166 | var validAmount = true,
167 | validAddress = true,
168 | newAmount;
169 | if (!amount) {
170 | newAmount = Math.floor(Number($iframe.find('#amount').val() * BTCMultiplier));
171 | } else {
172 | newAmount = amount;
173 | }
174 | var balance = wallet.getBalance();
175 | if (newAmount <= 0) {
176 | validAmount = false;
177 | } else if (newAmount + FEE > balance) {
178 | validAmount = false;
179 | }
180 |
181 | var newAddress;
182 | if (!address) {
183 | newAddress = $iframe.find('#address').val();
184 | if (!/^[13][1-9A-HJ-NP-Za-km-z]{26,33}$/.test(String(newAddress))) {
185 | validAddress = false;
186 | } else {
187 | try {
188 | new Bitcoin.Address(newAddress);
189 | } catch (e) {
190 | validAddress = false;
191 | }
192 | }
193 | } else {
194 | newAddress = address;
195 | }
196 |
197 | $iframe.find('#amount').parent().removeClass('has-error');
198 | $iframe.find('#address').parent().removeClass('has-error');
199 | $iframe.find('#password').parent().removeClass('has-error');
200 | if (!validAddress) {
201 | $iframe.find('#errorAlert').text('Invalid address').slideDown();
202 | $iframe.find('#address').parent().addClass('has-error');
203 | } else if (!validAmount) {
204 | $iframe.find('#errorAlert').text('Insufficient funds').slideDown();
205 | $iframe.find('#amount').parent().addClass('has-error');
206 | } else if (!navigator.onLine) {
207 | $iframe.find('#errorAlert').text('Connection offline').slideDown();
208 | $iframe.find('#amount').parent().addClass('has-error');
209 | } else {
210 | $(document).off('click.wallet contextmenu.wallet');
211 | $iframe.find('#errorAlert').slideUp();
212 | $iframe.find('#amount').parent().fadeOut('fast');
213 | $iframe.find('#address').parent().fadeOut('fast');
214 | $iframe.find('#password').parent().fadeOut('fast');
215 | $iframe.find('#button').fadeOut('fast', function () {
216 | $iframe.find('#progress').fadeIn('fast', function () {
217 | wallet.send(newAddress, newAmount, FEE, $iframe.find('#password').val()).then(function () {
218 | $iframe.find('#progress').fadeOut('fast', function () {
219 | $iframe.find('#successAlert').fadeIn('fast').delay(1000).fadeIn('fast', removeFrame);
220 | });
221 | }, function (e) {
222 | $iframe.find('#progress').fadeOut('fast', function () {
223 | if (e.message === 'Incorrect password') {
224 | $iframe.find('#password').parent().addClass('has-error');
225 | } else if (e.message === 'Insufficient funds') {
226 | $iframe.find('#amount').parent().addClass('has-error');
227 | }
228 | $iframe.find('#errorAlert').text(e.message).slideDown();
229 | if (!address) {
230 | $iframe.find('#address').parent().fadeIn();
231 | }
232 | if (!amount) {
233 | $iframe.find('#amount').parent().fadeIn();
234 | }
235 | if (wallet.isEncrypted()) {
236 | $iframe.find('#password').parent().fadeIn();
237 | }
238 | $iframe.find('#button').fadeIn();
239 | $(document).on('click.wallet contextmenu.wallet', removeFrame);
240 | });
241 | });
242 | });
243 | });
244 | }
245 | });
246 |
247 | $(document).on('click.wallet contextmenu.wallet', removeFrame);
248 |
249 | function removeFrame() {
250 | $(document).off('click.wallet contextmenu.wallet');
251 | $(iframe).fadeOut('fast', function () {
252 | $(this).remove();
253 | });
254 | }
255 | });
256 | }
257 |
258 |
259 | });
--------------------------------------------------------------------------------
/data/js/preferences.js:
--------------------------------------------------------------------------------
1 | /**
2 | * preferences.js
3 | * Copyright (c) 2014 Andrew Toth
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the MIT license.
7 | *
8 | * Preferences handles storing and retrieving saved values
9 | */
10 |
11 | (function (window) {
12 |
13 | var ADDRESS = "wallet.address",
14 | PRIVATE_KEY = "wallet.private_key",
15 | IS_ENCRYPTED = "wallet.is_encrypted",
16 | LAST_BALANCE = "wallet.last_balance",
17 | EXCHANGE_RATE = 'wallet.exchange_rate',
18 | BTC_UNITS = 'wallet.btc_units',
19 | CURRENCY = 'wallet.currency',
20 | preferences = function() {};
21 |
22 | function sync() {
23 | return new Promise(function (resolve) {
24 | // Different APIs for Chrome and Firefox
25 | if (typeof chrome !== 'undefined') {
26 | var object = {};
27 | object[ADDRESS] = '';
28 | object[PRIVATE_KEY] = '';
29 | object[IS_ENCRYPTED] = false;
30 | object[LAST_BALANCE] = 0;
31 | object[EXCHANGE_RATE] = 0;
32 | object[BTC_UNITS] = 'BTC';
33 | object[CURRENCY] = 'USD';
34 | chrome.storage.sync.get(object, resolve);
35 | } else {
36 | util.message('get').then(function (message) {
37 | if (typeof message[PRIVATE_KEY] === 'undefined') {
38 | message[ADDRESS] = '';
39 | message[PRIVATE_KEY] = '';
40 | message[IS_ENCRYPTED] = false;
41 | message[LAST_BALANCE] = 0;
42 | message[EXCHANGE_RATE] = 0;
43 | message[BTC_UNITS] = 'BTC';
44 | message[CURRENCY] = 'USD';
45 | return util.message('save', message);
46 | } else {
47 | return message;
48 | }
49 | }).then(function (message) {
50 | resolve(message);
51 | });
52 | }
53 | });
54 | }
55 |
56 | function get(pref) {
57 | return function () {
58 | return sync().then(function (values) {
59 | return values[pref];
60 | });
61 | };
62 | };
63 |
64 | function set(key, value) {
65 | return new Promise(function (resolve) {
66 | var object = {};
67 | object[key] = value;
68 | // Different APIs for Chrome and Firefox
69 | if (typeof chrome !== 'undefined') {
70 | chrome.storage.sync.set(object, resolve);
71 | } else {
72 | util.message('save', object).then(resolve);
73 | }
74 | });
75 | };
76 |
77 | preferences.prototype = {
78 |
79 | getAddress: get(ADDRESS),
80 | setAddress: function (address) {
81 | return set(ADDRESS, address);
82 | },
83 |
84 | getPrivateKey: get(PRIVATE_KEY),
85 | setPrivateKey: function (privateKey) {
86 | return set(PRIVATE_KEY, privateKey);
87 | },
88 |
89 | getIsEncrypted: get(IS_ENCRYPTED),
90 | setIsEncrypted: function (isEncrypted) {
91 | return set(IS_ENCRYPTED, isEncrypted);
92 | },
93 |
94 | getLastBalance: get(LAST_BALANCE),
95 | setLastBalance: function (lastBalance) {
96 | return set(LAST_BALANCE, lastBalance);
97 | },
98 |
99 | getExchangeRate: get(EXCHANGE_RATE),
100 | setExchangeRate: function (exchangeRate) {
101 | return set(EXCHANGE_RATE, exchangeRate);
102 | },
103 |
104 | getBTCUnits: get(BTC_UNITS),
105 | setBTCUnits: function (btcUnits) {
106 | return set(BTC_UNITS, btcUnits);
107 | },
108 |
109 | getCurrency: get(CURRENCY),
110 | setCurrency: function (currency) {
111 | return set(CURRENCY, currency).then(function () {
112 | currencyManager.updateExchangeRate();
113 | });
114 | }
115 | };
116 |
117 | window.preferences = new preferences();
118 |
119 | })(window);
--------------------------------------------------------------------------------
/data/js/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * util.js
3 | * Copyright (c) 2014 Andrew Toth
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the MIT license.
7 | *
8 | * Utility methods
9 | */
10 |
11 | (function (window) {
12 |
13 | var util = function () {},
14 | // Promisified ajax request
15 | request = function (url, type, data) {
16 | return new Promise(function (resolve, reject) {
17 | var req = new XMLHttpRequest();
18 | req.open((type ? type : 'GET'), url, true);
19 | req.onload = function () {
20 | if (req.status == 200) {
21 | resolve(req.response);
22 | } else {
23 | reject(Error(req.statusText));
24 | }
25 | }
26 | req.onerror = function () {
27 | reject(Error('Network error'));
28 | }
29 | if (type === 'POST') {
30 | req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
31 | }
32 | req.send(data);
33 | });
34 | };
35 |
36 | util.prototype = {
37 | getJSON: function (url) {
38 | if (typeof chrome !== 'undefined') {
39 | return request(url).then(JSON.parse);
40 | } else {
41 | return ret.message('getJSON', url);
42 | }
43 | },
44 |
45 | get: function (url) {
46 | return request(url);
47 | },
48 |
49 | post: function (url, data) {
50 | if (typeof chrome !== 'undefined') {
51 | return request(url, 'POST', data);
52 | } else {
53 | return ret.message('post', {url:url, content:data});
54 | }
55 | },
56 |
57 | // Used to send messages from content scripts to add-on scripts and return values to content scripts in Firefox add-on
58 | message: function (name, value) {
59 | return new Promise(function (resolve) {
60 | // 'self' can also be 'addon' depending on how script is injected
61 | var ref = (typeof addon === 'undefined' ? self : addon);
62 | ref.port.on(name, resolve);
63 | ref.port.emit(name, value);
64 | });
65 | }
66 | };
67 |
68 | var ret = new util();
69 |
70 | // Different workarounds to inject content into iFrames for Chrome and Firefox
71 | util.prototype.iframe = function (src) {
72 | return new Promise(function (resolve) {
73 | var iframe = document.createElement('iframe');
74 | document.body.appendChild(iframe);
75 | iframe.setAttribute('style', 'background-color: transparent; position: absolute; z-index: 2147483647; border: 0px;');
76 | iframe.setAttribute('allowtransparency', 'true');
77 | iframe.frameBorder = '0';
78 | if (typeof chrome !== 'undefined') {
79 | // For Chrome get the HTML content with an ajax call and write it into the document
80 | iframe.src = 'about:blank';
81 | var request = new XMLHttpRequest();
82 | request.open('GET', chrome.extension.getURL('data/' + src), false);
83 | request.send(null);
84 | var text = request.response;
85 | // Replace css relative locations with absolute locations since Chrome won't find relative
86 | text = text.replace(/css\//g, chrome.extension.getURL('') + 'data/css/');
87 | iframe.contentWindow.document.open('text/html', 'replace');
88 | iframe.contentWindow.document.write(text);
89 | iframe.contentWindow.document.close();
90 | resolve(iframe);
91 | } else {
92 | // For Firefox get the encoded HTML and set it to the iFrame's src
93 | ret.message('html', src).then(function (url) {
94 | iframe.src = url;
95 | // Only way to reliably know when the frame is ready in Firefox is by polling
96 | function pollReady() {
97 | if (!iframe.contentWindow.document.getElementById('progress')) {
98 | setTimeout(pollReady, 100);
99 | } else {
100 | resolve(iframe);
101 | }
102 | }
103 | pollReady();
104 | });
105 | }
106 | });
107 | }
108 |
109 | window.util = ret;
110 |
111 | })(window);
--------------------------------------------------------------------------------
/data/js/wallet.js:
--------------------------------------------------------------------------------
1 | /**
2 | * wallet.js
3 | * Copyright (c) 2014 Andrew Toth
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the MIT license.
7 | *
8 | * Wallet handles the address, private key and encryption,
9 | * as well as sending and determining balance
10 | */
11 |
12 | (function (window) {
13 | var balance = 0,
14 | address = '',
15 | privateKey = '',
16 | isEncrypted = false,
17 | websocket = null,
18 | balanceListener = null;
19 |
20 | var wallet = function () {};
21 | wallet.prototype = {
22 |
23 | getAddress: function () {
24 | return address;
25 | },
26 |
27 | getBalance: function () {
28 | return balance;
29 | },
30 |
31 | isEncrypted: function () {
32 | return isEncrypted;
33 | },
34 |
35 | // Balance listener gets called with new balance whenever it updates
36 | setBalanceListener: function (listener) {
37 | balanceListener = listener;
38 | },
39 |
40 | // Create a new address
41 | generateAddress: function (password) {
42 | return new Promise(function (resolve, reject) {
43 | if (ret.validatePassword(password)) {
44 | var eckey = new Bitcoin.ECKey(false);
45 | if (isEncrypted) {
46 | if (typeof chrome !== 'undefined') {
47 | privateKey = CryptoJS.AES.encrypt(eckey.getExportedPrivateKey(), password);
48 | } else {
49 | privateKey = JSON.parse(CryptoJS.AES.encrypt(eckey.getExportedPrivateKey(), password, {format:jsonFormatter}));
50 | }
51 | } else {
52 | privateKey = eckey.getExportedPrivateKey();
53 | }
54 | address = eckey.getBitcoinAddress().toString();
55 | balance = 0;
56 | Promise.all([preferences.setAddress(address), preferences.setPrivateKey(privateKey), preferences.setIsEncrypted(isEncrypted)]).then(function () {
57 | updateBalance()
58 | resolve();
59 | });
60 | } else {
61 | reject(Error('Incorrect password'));
62 | }
63 | });
64 | },
65 |
66 | // Restore the previously saved address
67 | restoreAddress: function () {
68 | return new Promise(function (resolve, reject) {
69 | Promise.all([preferences.getAddress(), preferences.getPrivateKey(), preferences.getIsEncrypted()]).then(function (values) {
70 | if (values[0].length > 0) {
71 | address = values[0];
72 | privateKey = values[1];
73 | isEncrypted = values[2];
74 | updateBalance();
75 | resolve();
76 | } else {
77 | reject(Error('No address'));
78 | }
79 | });
80 | });
81 | },
82 |
83 | // Import an address using a private key
84 | importAddress: function (password, _privateKey) {
85 | return new Promise(function (resolve, reject) {
86 | if (ret.validatePassword(password)) {
87 | try {
88 | var eckey = new Bitcoin.ECKey(_privateKey);
89 | if (isEncrypted) {
90 | if (typeof chrome !== 'undefined') {
91 | privateKey = CryptoJS.AES.encrypt(eckey.getExportedPrivateKey(), password);
92 | } else {
93 | privateKey = JSON.parse(CryptoJS.AES.encrypt(eckey.getExportedPrivateKey(), password, {format:jsonFormatter}));
94 | }
95 | } else {
96 | privateKey = eckey.getExportedPrivateKey();
97 | }
98 | address = eckey.getBitcoinAddress().toString();
99 | balance = 0;
100 | Promise.all([preferences.setAddress(address), preferences.setPrivateKey(privateKey), preferences.setLastBalance(0)]).then(function () {
101 | updateBalance();
102 | resolve();
103 | });
104 | } catch (e) {
105 | reject(Error('Invalid private key'));
106 | }
107 | } else {
108 | reject(Error('Incorrect password'));
109 | }
110 | });
111 | },
112 |
113 | // Check if the password is valid
114 | validatePassword: function (password) {
115 | if (isEncrypted) {
116 | try {
117 | // If we can decrypt the private key with the password, then the password is correct
118 | // We never store a copy of the password anywhere
119 | if (typeof chrome !== 'undefined') {
120 | return CryptoJS.AES.decrypt(privateKey, password).toString(CryptoJS.enc.Utf8);
121 | } else {
122 | return CryptoJS.AES.decrypt(JSON.stringify(privateKey), password, {format:jsonFormatter}).toString(CryptoJS.enc.Utf8);
123 | }
124 | } catch (e) {
125 | return false;
126 | }
127 | } else {
128 | return true;
129 | }
130 | },
131 |
132 | // Return a decrypted private key using the password
133 | getDecryptedPrivateKey: function (password) {
134 | if (isEncrypted) {
135 | if (typeof chrome !== 'undefined') {
136 | var decryptedPrivateKey = CryptoJS.AES.decrypt(privateKey, password);
137 | } else {
138 | var decryptedPrivateKey = CryptoJS.AES.decrypt(JSON.stringify(privateKey), password, {format:jsonFormatter});
139 | }
140 | try {
141 | if (!decryptedPrivateKey.toString(CryptoJS.enc.Utf8)) {
142 | return null;
143 | }
144 | } catch (e) {
145 | return null;
146 | }
147 | return decryptedPrivateKey.toString(CryptoJS.enc.Utf8);
148 | } else {
149 | return privateKey;
150 | }
151 | }
152 |
153 | };
154 |
155 | // Gets the current balance and sets up a websocket to monitor new transactions
156 | function updateBalance() {
157 | // Make sure we have an address
158 | if (address.length) {
159 | // Last stored balance is the fastest way to update
160 | preferences.getLastBalance().then(function (result) {
161 | balance = result;
162 | if (balanceListener) balanceListener(balance);
163 | // Check blockchain.info for the current balance
164 | util.get('https://blockchain.info/q/addressbalance/' + address).then(function (response) {
165 | balance = response;
166 | return preferences.setLastBalance(balance);
167 | }).then(function () {
168 | if (balanceListener) balanceListener(balance);
169 | // Close the websocket if it was still open
170 | if (websocket) {
171 | websocket.close();
172 | }
173 | // Create a new websocket to blockchain.info
174 | websocket = new WebSocket("ws://ws.blockchain.info:8335/inv");
175 | websocket.onopen = function() {
176 | // Tell the websocket we want to monitor the address
177 | websocket.send('{"op":"addr_sub", "addr":"' + address + '"}');
178 | };
179 | websocket.onmessage = function (evt) {
180 | // Parse the new transaction
181 | var json = JSON.parse(evt.data);
182 | var inputs = json.x.inputs;
183 | var outputs = json.x.out;
184 | var i;
185 | // Subtract all inputs from the balance
186 | for (i = 0; i < inputs.length; i++) {
187 | var input = inputs[i].prev_out;
188 | if (input.addr === address) {
189 | balance = Number(balance) - Number(input.value);
190 | }
191 | }
192 | // Add all output to the balance
193 | for (i = 0; i < outputs.length; i++) {
194 | var output = outputs[i];
195 | if (output.addr === address) {
196 | balance = Number(balance) + Number(output.value);
197 | }
198 | }
199 | // Save the new balance and notify the listener
200 | preferences.setLastBalance(balance).then(function () {
201 | if (balanceListener) balanceListener(balance);
202 | });
203 | };
204 | });
205 | });
206 | }
207 | }
208 |
209 | var ret = new wallet();
210 |
211 | // Change the password to a new password
212 | wallet.prototype.updatePassword = function (password, newPassword) {
213 | return new Promise(function (resolve, reject) {
214 | // Make sure the previous password is correct
215 | var decryptedPrivateKey = ret.getDecryptedPrivateKey(password);
216 | if (decryptedPrivateKey) {
217 | // If we have a new password we use it, otherwise leave cleartext
218 | if (newPassword) {
219 | if (typeof chrome !== 'undefined') {
220 | privateKey = CryptoJS.AES.encrypt(eckey.getExportedPrivateKey(), newPassword);
221 | } else {
222 | privateKey = JSON.parse(CryptoJS.AES.encrypt(decryptedPrivateKey, newPassword, {format:jsonFormatter}));
223 | }
224 | isEncrypted = true;
225 | } else {
226 | privateKey = decryptedPrivateKey;
227 | isEncrypted = false;
228 | }
229 | // Save the encrypted private key
230 | // Passwords are never saved anywhere
231 | Promise.all([preferences.setIsEncrypted(isEncrypted), preferences.setPrivateKey(privateKey)]).then(resolve);
232 | } else {
233 | reject(Error('Incorrect password'));
234 | }
235 | });
236 | };
237 |
238 | // Send bitcoin from the wallet to another address
239 | wallet.prototype.send = function (sendAddress, amount, fee, password) {
240 | return new Promise(function (resolve, reject) {
241 | var decryptedPrivateKey = ret.getDecryptedPrivateKey(password);
242 | if (decryptedPrivateKey) {
243 | // Get all unspent outputs from blockchain.info to generate our inputs
244 | util.getJSON('https://blockchain.info/unspent?address=' + address).then(function (json) {
245 | var inputs = json.unspent_outputs,
246 | selectedOuts = [],
247 | eckey = new Bitcoin.ECKey(decryptedPrivateKey),
248 | // Total cost is amount plus fee
249 | totalInt = Number(amount) + Number(fee),
250 | txValue = new BigInteger('' + totalInt, 10),
251 | availableValue = BigInteger.ZERO;
252 | // Gather enough inputs so that their value is greater than or equal to the total cost
253 | for (var i = 0; i < inputs.length; i++) {
254 | selectedOuts.push(inputs[i]);
255 | availableValue = availableValue.add(new BigInteger('' + inputs[i].value, 10));
256 | if (availableValue.compareTo(txValue) >= 0) break;
257 | }
258 | // If there aren't enough unspent outputs to available then we can't send the transaction
259 | if (availableValue.compareTo(txValue) < 0) {
260 | reject(Error('Insufficient funds'));
261 | } else {
262 | // Create the transaction
263 | var sendTx = new Bitcoin.Transaction();
264 | // Add all our unspent outputs to the transaction as the inputs
265 | for (i = 0; i < selectedOuts.length; i++) {
266 | var hash = Crypto.util.bytesToBase64(Crypto.util.hexToBytes(selectedOuts[i].tx_hash));
267 | var script = new Bitcoin.Script(Crypto.util.hexToBytes(selectedOuts[i].script));
268 | var txin = new Bitcoin.TransactionIn({
269 | outpoint: {
270 | hash: hash,
271 | index: selectedOuts[i].tx_output_n
272 | },
273 | script: script,
274 | sequence: 4294967295
275 | });
276 | sendTx.addInput(txin);
277 | }
278 | // Add the send address to the transaction as the output
279 | sendTx.addOutput(new Bitcoin.Address(sendAddress), new BigInteger('' + amount, 10));
280 | // Add any leftover value to the transaction as an output pointing back to this wallet,
281 | // minus the fee of course
282 | var changeValue = availableValue.subtract(txValue);
283 | if (changeValue.compareTo(BigInteger.ZERO) > 0) {
284 | sendTx.addOutput(eckey.getBitcoinAddress(), changeValue);
285 | }
286 | // Sign all the input hashes
287 | var hashType = 1; // SIGHASH_ALL
288 | for (i = 0; i < sendTx.ins.length; i++) {
289 | var connectedScript = sendTx.ins[i].script;
290 | hash = sendTx.hashTransactionForSignature(connectedScript, i, hashType);
291 | var signature = eckey.sign(hash);
292 | signature.push(parseInt(hashType, 10));
293 | var pubKey = eckey.getPub();
294 | script = new Bitcoin.Script();
295 | script.writeBytes(signature);
296 | script.writeBytes(pubKey);
297 | sendTx.ins[i].script = script;
298 | }
299 | // Push the transaction to blockchain.info
300 | var data = 'tx=' + Crypto.util.bytesToHex(sendTx.serialize());
301 | util.post('https://blockchain.info/pushtx', data).then(function () {
302 | // Notify the balance listener of the changed amount immediately,
303 | // but don't set the balance since the transaction will be processed by the websocket
304 | if (balanceListener) balanceListener(balance - amount - fee);
305 | resolve();
306 | }, function () {
307 | reject(Error('Unknown error'));
308 | });
309 | }
310 | }, function () {
311 | reject(Error('Unknown error'));
312 | });
313 | } else {
314 | reject(Error('Incorrect password'));
315 | }
316 | });
317 | };
318 |
319 | var jsonFormatter = {
320 | stringify: function (cipherParams) {
321 | // create json object with ciphertext
322 | var jsonObj = {
323 | ct: cipherParams.ciphertext.toString(CryptoJS.enc.Hex)
324 | };
325 |
326 | // optionally add iv and salt
327 | if (cipherParams.iv) {
328 | jsonObj.iv = cipherParams.iv.toString();
329 | }
330 | if (cipherParams.salt) {
331 | jsonObj.s = cipherParams.salt.toString();
332 | }
333 |
334 | // stringify json object
335 | return JSON.stringify(jsonObj);
336 | },
337 |
338 | parse: function (jsonStr) {
339 | // parse json string
340 | var jsonObj = JSON.parse(jsonStr);
341 |
342 | // extract ciphertext from json object, and create cipher params object
343 | var cipherParams = CryptoJS.lib.CipherParams.create({
344 | ciphertext: CryptoJS.enc.Hex.parse(jsonObj.ct)
345 | });
346 |
347 | // optionally extract iv and salt
348 | if (jsonObj.iv) {
349 | cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv)
350 | }
351 | if (jsonObj.s) {
352 | cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s)
353 | }
354 |
355 | return cipherParams;
356 | }
357 | };
358 |
359 | window.wallet = ret;
360 | })(window);
--------------------------------------------------------------------------------
/data/paypopup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bitcoin Wallet Extension
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | Success
16 |
17 |
18 | Not a valid address.
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Send
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/lib/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * main.js
3 | * Copyright (c) 2014 Andrew Toth
4 | *
5 | * This program is free software; you can redistribute it and/or modify
6 | * it under the terms of the MIT license.
7 | *
8 | * Main file for Firefox add-on
9 | */
10 |
11 | (function () {
12 | var self = require('sdk/self'),
13 | data = self.data
14 |
15 | // Create the wallet panel
16 | var walletPanel = require('sdk/panel').Panel({
17 | width:362,
18 | height:278,
19 | contentURL: data.url('index.html'),
20 | onShow: function () {
21 | walletPanel.port.emit('show');
22 | walletPanel.port.emit('version', self.version);
23 | }
24 | });
25 | addListeners(walletPanel);
26 |
27 | walletPanel.port.on('resize', function (height) {
28 | walletPanel.resize(walletPanel.width, height);
29 | });
30 |
31 | // Attach the wallet to the bitcoin button
32 | require('sdk/widget').Widget({
33 | id: 'open-wallet-btn',
34 | label: 'Bitcoin Wallet',
35 | contentURL: data.url('bitcoin38.png'),
36 | panel: walletPanel
37 | });
38 |
39 | // Inject the hover popup scripts into every page
40 | require('sdk/page-mod').PageMod({
41 | include: '*',
42 | contentScriptFile: [
43 | data.url('js/libs/promise.min.js'),
44 | data.url('js/libs/jquery.min.js'),
45 | data.url('js/libs/bitcoinjs-lib.min.js'),
46 | data.url('js/util.js'),
47 | data.url('js/preferences.js'),
48 | data.url('js/currency-manager.js'),
49 | data.url('js/hoverpopup.js')],
50 | onAttach: function (worker) {
51 | addListeners(worker);
52 | }
53 | });
54 |
55 | var tabs = require('sdk/tabs');
56 |
57 | // Add listeners to the worker to communicate with scripts
58 | function addListeners(worker) {
59 | // Get prefs from storage
60 | worker.port.on('get', function () {
61 | var storage = require('sdk/simple-storage').storage;
62 | worker.port.emit('get', storage);
63 | });
64 |
65 | // Save prefs to storage
66 | worker.port.on('save', function (object) {
67 | var storage = require('sdk/simple-storage').storage;
68 | for (var i in object) {
69 | storage[i] = object[i];
70 | }
71 | worker.port.emit('save', storage);
72 | });
73 |
74 | // Open tabs
75 | worker.port.on('openTab', function (url) {
76 | tabs.open(url);
77 | });
78 |
79 | // Get HTML for local files
80 | worker.port.on('html', function (url) {
81 | let content = data.load(url);
82 | // Replace relative paths of css files to absolute path
83 | content = content.replace(/css\//g, data.url('css/'));
84 | content = encodeURIComponent(content);
85 | content = 'data:text/html;charset=utf-8,' + content;
86 | worker.port.emit('html', content);
87 | });
88 |
89 | // Cross-domain XHRs
90 | worker.port.on('getJSON', function (url) {
91 | require("sdk/request").Request({
92 | url: url,
93 | onComplete: function (response) {
94 | worker.port.emit('getJSON', response.json);
95 | }
96 | }).get();
97 | });
98 |
99 | worker.port.on('post', function (message) {
100 | require("sdk/request").Request({
101 | url: message.url,
102 | content: message.content,
103 | onComplete: function (response) {
104 | worker.port.emit('post', response);
105 | }
106 | }).post();
107 | });
108 | }
109 |
110 | var workers = {};
111 |
112 | // Inject pay popup scripts into every page
113 | tabs.on('ready', function (tab) {
114 | workers[tab.id] = tab.attach({
115 | contentScriptFile: [
116 | data.url('js/libs/promise.min.js'),
117 | data.url('js/libs/jquery.min.js'),
118 | data.url('js/libs/cryptojs.min.js'),
119 | data.url('js/libs/bitcoinjs-lib.min.js'),
120 | data.url('js/util.js'),
121 | data.url('js/preferences.js'),
122 | data.url('js/currency-manager.js'),
123 | data.url('js/wallet.js'),
124 | data.url('js/paypopup.js')]
125 | });
126 | addListeners(workers[tab.id]);
127 | });
128 |
129 |
130 | var cm = require('sdk/context-menu');
131 |
132 | // Create the context menu and inject the scripts to control it
133 | cm.Item({
134 | label: 'Send BTC',
135 | image: data.url('bitcoin38.png'),
136 | context: cm.SelectorContext('*'),
137 | contentScriptFile: [
138 | data.url('js/libs/bitcoinjs-lib.min.js'),
139 | data.url('js/context-menu.js')
140 | ],
141 | onMessage: function (message) {
142 | if (message.address === null) {
143 | this.label = 'Send BTC';
144 | } else if (typeof message.address === 'string') {
145 | this.label = 'Pay ' + message.address;
146 | }
147 | if (message.clicked) {
148 | var worker = workers[tabs.activeTab.id];
149 | worker.port.emit('pay', message);
150 | }
151 | }
152 | });
153 |
154 | })();
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 |
4 | "name": "BitBrowser Bitcoin Wallet",
5 | "author": "Andrew Toth",
6 | "description": "Bitcoin wallet in the browser. Send and receive instantly on any web page.",
7 | "version": "1.1",
8 |
9 | "icons": {
10 | "16": "data/bitcoin16.png",
11 | "48": "data/bitcoin48.png",
12 | "128": "data/bitcoin128.png"
13 | },
14 |
15 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self';",
16 |
17 | "permissions": ["contextMenus", "activeTab", "", "storage"],
18 |
19 | "content_scripts": [
20 | {
21 | "matches": [""],
22 | "js": [
23 | "data/js/libs/jquery.min.js",
24 | "data/js/libs/cryptojs.min.js",
25 | "data/js/libs/bitcoinjs-lib.min.js",
26 | "data/js/util.js",
27 | "data/js/preferences.js",
28 | "data/js/currency-manager.js",
29 | "data/js/wallet.js",
30 | "data/js/paypopup.js",
31 | "data/js/hoverpopup.js"
32 | ],
33 | "all_frames": true
34 | }
35 | ],
36 |
37 | "background": {
38 | "scripts": ["data/js/background.js"]
39 | },
40 |
41 | "web_accessible_resources": [
42 | "data/*"
43 | ],
44 |
45 | "browser_action": {
46 | "default_icon": {
47 | "19": "data/bitcoin19.png",
48 | "38": "data/bitcoin38.png"
49 | },
50 | "default_title": "Bitcoin Wallet",
51 | "default_popup": "data/index.html"
52 | }
53 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitcoinwallet",
3 | "title": "BitBrowser Bitcoin Wallet",
4 | "id": "jid1-sqPdj54n2PcVOQ",
5 | "description": "Bitcoin wallet in the browser. Send and receive instantly on any web page.",
6 | "author": "Andrew Toth",
7 | "license": "MIT",
8 | "version": "1.1",
9 | "permissions": {
10 | "cross-domain-content": ["https://blockchain.info/", "https://api.bitcoinaverage.com/"]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------