|
14 | 14 | coinjs.priv = 0x80; |
15 | 15 | coinjs.multisig = 0x05; |
16 | 16 | coinjs.hdkey = {'prv':0x0488ade4, 'pub':0x0488b21e}; |
| 17 | + coinjs.bech32 = {'charset':'qpzry9x8gf2tvdw0s3jn54khce6mua7l', 'version':0, 'hrp':'bc'}; |
17 | 18 |
|
18 | 19 | coinjs.compressed = false; |
19 | 20 |
|
|
184 | 185 | return {'address':address, 'type':'segwit', 'redeemscript':Crypto.util.bytesToHex(keyhash)}; |
185 | 186 | } |
186 | 187 |
|
| 188 | + /* create a new segwit bech32 encoded address */ |
| 189 | + coinjs.bech32Address = function(pubkey){ |
| 190 | + var program = ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubkey), {asBytes: true}), {asBytes: true}); |
| 191 | + var address = coinjs.bech32_encode(coinjs.bech32.hrp, [coinjs.bech32.version].concat(coinjs.bech32_convert(program, 8, 5, true))); |
| 192 | + return {'address':address, 'type':'bech32', 'redeemscript':Crypto.util.bytesToHex(program)}; |
| 193 | + } |
| 194 | + |
| 195 | + /* extract the redeemscript from a bech32 address */ |
| 196 | + coinjs.bech32redeemscript = function(address){ |
| 197 | + var r = false; |
| 198 | + var decode = coinjs.bech32_decode(address); |
| 199 | + if(decode){ |
| 200 | + decode.data.shift(); |
| 201 | + return Crypto.util.bytesToHex(coinjs.bech32_convert(decode.data, 5, 8, true)); |
| 202 | + } |
| 203 | + return r; |
| 204 | + } |
| 205 | + |
187 | 206 | /* provide a privkey and return an WIF */ |
188 | 207 | coinjs.privkey2wif = function(h){ |
189 | 208 | var r = Crypto.util.hexToBytes(h); |
|
286 | 305 | return false; |
287 | 306 | } |
288 | 307 | } catch(e) { |
289 | | - return false; |
| 308 | + bech32rs = coinjs.bech32redeemscript(addr); |
| 309 | + if(bech32rs){ |
| 310 | + return {'type':'bech32', 'redeemscript':bech32rs}; |
| 311 | + } else { |
| 312 | + return false; |
| 313 | + } |
290 | 314 | } |
291 | 315 | } |
292 | 316 |
|
|
316 | 340 | return false; |
317 | 341 | } |
318 | 342 |
|
| 343 | + coinjs.bech32_polymod = function(values) { |
| 344 | + var chk = 1; |
| 345 | + var BECH32_GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; |
| 346 | + for (var p = 0; p < values.length; ++p) { |
| 347 | + var top = chk >> 25; |
| 348 | + chk = (chk & 0x1ffffff) << 5 ^ values[p]; |
| 349 | + for (var i = 0; i < 5; ++i) { |
| 350 | + if ((top >> i) & 1) { |
| 351 | + chk ^= BECH32_GENERATOR[i]; |
| 352 | + } |
| 353 | + } |
| 354 | + } |
| 355 | + return chk; |
| 356 | + } |
| 357 | + |
| 358 | + coinjs.bech32_hrpExpand = function(hrp) { |
| 359 | + var ret = []; |
| 360 | + var p; |
| 361 | + for (p = 0; p < hrp.length; ++p) { |
| 362 | + ret.push(hrp.charCodeAt(p) >> 5); |
| 363 | + } |
| 364 | + ret.push(0); |
| 365 | + for (p = 0; p < hrp.length; ++p) { |
| 366 | + ret.push(hrp.charCodeAt(p) & 31); |
| 367 | + } |
| 368 | + return ret; |
| 369 | + } |
| 370 | + |
| 371 | + coinjs. bech32_verifyChecksum = function(hrp, data) { |
| 372 | + return coinjs.bech32_polymod(coinjs.bech32_hrpExpand(hrp).concat(data)) === 1; |
| 373 | + } |
| 374 | + |
| 375 | + coinjs.bech32_createChecksum = function(hrp, data) { |
| 376 | + var values = coinjs.bech32_hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); |
| 377 | + var mod = coinjs.bech32_polymod(values) ^ 1; |
| 378 | + var ret = []; |
| 379 | + for (var p = 0; p < 6; ++p) { |
| 380 | + ret.push((mod >> 5 * (5 - p)) & 31); |
| 381 | + } |
| 382 | + return ret; |
| 383 | + } |
| 384 | + |
| 385 | + coinjs.bech32_encode = function(hrp, data) { |
| 386 | + var combined = data.concat(coinjs.bech32_createChecksum(hrp, data)); |
| 387 | + var ret = hrp + '1'; |
| 388 | + for (var p = 0; p < combined.length; ++p) { |
| 389 | + ret += coinjs.bech32.charset.charAt(combined[p]); |
| 390 | + } |
| 391 | + return ret; |
| 392 | + } |
| 393 | + |
| 394 | + coinjs.bech32_decode = function(bechString) { |
| 395 | + var p; |
| 396 | + var has_lower = false; |
| 397 | + var has_upper = false; |
| 398 | + for (p = 0; p < bechString.length; ++p) { |
| 399 | + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { |
| 400 | + return null; |
| 401 | + } |
| 402 | + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { |
| 403 | + has_lower = true; |
| 404 | + } |
| 405 | + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { |
| 406 | + has_upper = true; |
| 407 | + } |
| 408 | + } |
| 409 | + if (has_lower && has_upper) { |
| 410 | + return null; |
| 411 | + } |
| 412 | + bechString = bechString.toLowerCase(); |
| 413 | + var pos = bechString.lastIndexOf('1'); |
| 414 | + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { |
| 415 | + return null; |
| 416 | + } |
| 417 | + var hrp = bechString.substring(0, pos); |
| 418 | + var data = []; |
| 419 | + for (p = pos + 1; p < bechString.length; ++p) { |
| 420 | + var d = coinjs.bech32.charset.indexOf(bechString.charAt(p)); |
| 421 | + if (d === -1) { |
| 422 | + return null; |
| 423 | + } |
| 424 | + data.push(d); |
| 425 | + } |
| 426 | + if (!coinjs.bech32_verifyChecksum(hrp, data)) { |
| 427 | + return null; |
| 428 | + } |
| 429 | + return { |
| 430 | + hrp: hrp, |
| 431 | + data: data.slice(0, data.length - 6) |
| 432 | + }; |
| 433 | + } |
| 434 | + |
| 435 | + coinjs.bech32_convert = function(data, inBits, outBits, pad) { |
| 436 | + var value = 0; |
| 437 | + var bits = 0; |
| 438 | + var maxV = (1 << outBits) - 1; |
| 439 | + |
| 440 | + var result = []; |
| 441 | + for (var i = 0; i < data.length; ++i) { |
| 442 | + value = (value << inBits) | data[i]; |
| 443 | + bits += inBits; |
| 444 | + |
| 445 | + while (bits >= outBits) { |
| 446 | + bits -= outBits; |
| 447 | + result.push((value >> bits) & maxV); |
| 448 | + } |
| 449 | + } |
| 450 | + |
| 451 | + if (pad) { |
| 452 | + if (bits > 0) { |
| 453 | + result.push((value << (outBits - bits)) & maxV); |
| 454 | + } |
| 455 | + } else { |
| 456 | + if (bits >= inBits) throw new Error('Excess padding'); |
| 457 | + if ((value << (outBits - bits)) & maxV) throw new Error('Non-zero padding'); |
| 458 | + } |
| 459 | + |
| 460 | + return result; |
| 461 | + } |
| 462 | + |
319 | 463 | coinjs.testdeterministicK = function() { |
320 | 464 | // https://github.com/bitpay/bitcore/blob/9a5193d8e94b0bd5b8e7f00038e7c0b935405a03/test/crypto/ecdsa.js |
321 | 465 | // Line 21 and 22 specify digest hash and privkey for the first 2 test vectors. |
|
728 | 872 | r.spendToScript = function(address){ |
729 | 873 | var addr = coinjs.addressDecode(address); |
730 | 874 | var s = coinjs.script(); |
731 | | - if(addr.version==coinjs.multisig){ // multisig address |
| 875 | + if(addr.type == "bech32"){ |
| 876 | + s.writeOp(0); |
| 877 | + s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript)); |
| 878 | + } else if(addr.version==coinjs.multisig){ // multisig address |
732 | 879 | s.writeOp(169); //OP_HASH160 |
733 | 880 | s.writeBytes(addr.bytes); |
734 | 881 | s.writeOp(135); //OP_EQUAL |
|
1043 | 1190 | return {'result':0, 'fail':'redeemscript', 'response':'redeemscript missing or not valid for segwit'}; |
1044 | 1191 | } |
1045 | 1192 |
|
1046 | | - var scriptcode = Crypto.util.hexToBytes(extract['script']); |
1047 | | - if(scriptcode[0] != 0){ |
1048 | | - return {'result':0, 'fail':'scriptcode', 'response':'redeemscript is not valid'}; |
1049 | | - } |
1050 | | - |
1051 | 1193 | if(extract['value'] == -1){ |
1052 | 1194 | return {'result':0, 'fail':'value', 'response':'unable to generate a valid segwit hash without a value'}; |
1053 | 1195 | } |
1054 | 1196 |
|
| 1197 | + var scriptcode = Crypto.util.hexToBytes(extract['script']); |
| 1198 | + |
1055 | 1199 | // end of redeem script check |
1056 | 1200 |
|
1057 | | - scriptcode = scriptcode.slice(1); |
1058 | | - scriptcode.unshift(25, 118, 169); |
1059 | | - scriptcode.push(136, 172); |
| 1201 | + /* P2WPKH */ |
| 1202 | + if(scriptcode.length == 20){ |
| 1203 | + scriptcode = [0x00,0x14].concat(scriptcode); |
| 1204 | + } |
| 1205 | + |
| 1206 | + if(scriptcode.length == 22){ |
| 1207 | + scriptcode = scriptcode.slice(1); |
| 1208 | + scriptcode.unshift(25, 118, 169); |
| 1209 | + scriptcode.push(136, 172); |
| 1210 | + } |
1060 | 1211 |
|
1061 | 1212 | var value = coinjs.numToBytes(extract['value'], 8); |
1062 | 1213 |
|
|
1137 | 1288 | } else if(this.ins[index].script.chunks.length == 5 && this.ins[index].script.chunks[1] == 177){//OP_CHECKLOCKTIMEVERIFY |
1138 | 1289 | // hodl script (not signed) |
1139 | 1290 | return {'type':'hodl', 'signed':'false', 'signatures': 0, 'script': Crypto.util.bytesToHex(this.ins[index].script.buffer)}; |
1140 | | - } else if((this.ins[index].script.chunks.length <= 3 && this.ins[index].script.chunks.length > 0) && this.ins[index].script.chunks[0].length == 22 && this.ins[index].script.chunks[0][0] == 0){ |
| 1291 | + } else if((this.ins[index].script.chunks.length <= 3 && this.ins[index].script.chunks.length > 0) && ((this.ins[index].script.chunks[0].length == 22 && this.ins[index].script.chunks[0][0] == 0) || (this.ins[index].script.chunks[0].length == 20 && this.ins[index].script.chunks[1] == 0))){ |
| 1292 | + var signed = ((this.witness[index]) && this.witness[index].length==2) ? 'true' : 'false'; |
| 1293 | + var sigs = (signed == 'true') ? 1 : 0; |
| 1294 | + var value = -1; // no value found |
| 1295 | + if((this.ins[index].script.chunks[2]) && this.ins[index].script.chunks[2].length==8){ |
| 1296 | + value = coinjs.bytesToNum(this.ins[index].script.chunks[2]); // value found encoded in transaction (THIS IS NON STANDARD) |
| 1297 | + } |
| 1298 | + return {'type':'segwit', 'signed':signed, 'signatures': sigs, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]), 'value': value}; |
| 1299 | + |
| 1300 | + /* } else if((this.ins[index].script.chunks.length <= 3 && this.ins[index].script.chunks.length > 0) && (this.ins[index].script.chunks[0].length == 22 && this.ins[index].script.chunks[0][0] == 0)){ |
| 1301 | + alert('p2sh'); |
1141 | 1302 | // segwit script |
1142 | 1303 | var signed = ((this.witness[index]) && this.witness[index].length==2) ? 'true' : 'false'; |
1143 | 1304 | var sigs = (signed == 'true') ? 1 : 0; |
|
1146 | 1307 | value = coinjs.bytesToNum(this.ins[index].script.chunks[2]); // value found encoded in transaction (THIS IS NON STANDARD) |
1147 | 1308 | } |
1148 | 1309 | return {'type':'segwit', 'signed':signed, 'signatures': sigs, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[0]), 'value': value}; |
| 1310 | + */ |
1149 | 1311 | } else if (this.ins[index].script.chunks[0]==0 && this.ins[index].script.chunks[this.ins[index].script.chunks.length-1][this.ins[index].script.chunks[this.ins[index].script.chunks.length-1].length-1]==174) { // OP_CHECKMULTISIG |
1150 | 1312 | // multisig script, with signature(s) included |
1151 | 1313 | return {'type':'multisig', 'signed':'true', 'signatures':this.ins[index].script.chunks.length-2, 'script': Crypto.util.bytesToHex(this.ins[index].script.chunks[this.ins[index].script.chunks.length-1])}; |
|
1367 | 1529 |
|
1368 | 1530 | var wif2 = coinjs.wif2pubkey(wif); |
1369 | 1531 | var segwit = coinjs.segwitAddress(wif2['pubkey']); |
| 1532 | + var bech32 = coinjs.bech32Address(wif2['pubkey']); |
1370 | 1533 |
|
1371 | | - if(segwit['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0])){ |
| 1534 | + if((segwit['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0])) || (bech32['redeemscript'] == Crypto.util.bytesToHex(this.ins[index].script.chunks[0]))){ |
1372 | 1535 | var txhash = this.transactionHashSegWitV0(index, shType); |
| 1536 | + |
1373 | 1537 | if(txhash.result == 1){ |
| 1538 | + |
1374 | 1539 | var segwitHash = Crypto.util.hexToBytes(txhash.hash); |
1375 | 1540 | var signature = this.transactionSig(index, wif, shType, segwitHash); |
1376 | | - |
| 1541 | + |
1377 | 1542 | // remove any non standard data we store, i.e. input value |
1378 | 1543 | var script = coinjs.script(); |
1379 | 1544 | script.writeBytes(this.ins[index].script.chunks[0]); |
|
1396 | 1561 | for(var y = 0; y < this.witness.length; y++){ |
1397 | 1562 | if(!witness_used.includes(y)){ |
1398 | 1563 | var sw = coinjs.segwitAddress(this.witness[y][1]); |
1399 | | - if(sw['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])){ |
| 1564 | + var b32 = coinjs.bech32Address(this.witness[y][1]); |
| 1565 | + if((sw['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])) || (b32['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0]))){ |
1400 | 1566 | witness_order.push(this.witness[y]); |
1401 | 1567 | witness_used.push(y); |
| 1568 | + if(b32['redeemscript'] == Crypto.util.bytesToHex(this.ins[i].script.chunks[0])){ |
| 1569 | + this.ins[index].script = coinjs.script(); |
| 1570 | + } |
1402 | 1571 | break; |
1403 | 1572 | } |
1404 | 1573 | } |
|
0 commit comments