diff --git a/scripts/btcOperator.js b/scripts/btcOperator.js index 2abb80f..0d54c23 100644 --- a/scripts/btcOperator.js +++ b/scripts/btcOperator.js @@ -92,6 +92,9 @@ }, bech32Address: { value: key => coinjs.bech32Address(btcOperator.pubkey(key)).address + }, + bech32mAddress: { + value: key => segwit_addr.encode("bc",1,key) } }); @@ -107,6 +110,8 @@ return btcOperator.segwitAddress(key) === addr; case "bech32": return btcOperator.bech32Address(key) === addr; + case "bech32m": + return btcOperator.bech32mAddress(key) === addr; // Key is a byte array of 32 bytes default: return null; } @@ -116,7 +121,7 @@ if (!addr) return undefined; let type = coinjs.addressDecode(addr).type; - if (["standard", "multisig", "bech32", "multisigBech32"].includes(type)) + if (["standard", "multisig", "bech32", "multisigBech32","bech32m"].includes(type)) return type; else return false; @@ -281,6 +286,7 @@ BECH32_OUTPUT_SIZE = 23, BECH32_MULTISIG_OUTPUT_SIZE = 34, SEGWIT_OUTPUT_SIZE = 23; + BECH32M_OUTPUT_SIZE = 35; // Check this later function _redeemScript(addr, key) { let decode = coinjs.addressDecode(addr); @@ -291,6 +297,8 @@ return key ? coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript : null; case "bech32": return decode.redeemscript; + case "bech32m": + return decode.outstring; //Maybe the redeemscript will come when input processing happens for bech32m default: return null; } @@ -328,6 +336,8 @@ return BASE_OUTPUT_SIZE + BECH32_MULTISIG_OUTPUT_SIZE; case "multisig": return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE; + case "bech32m": + return BASE_OUTPUT_SIZE + BECH32M_OUTPUT_SIZE; default: return null; } diff --git a/scripts/lib.js b/scripts/lib.js index e383403..042c730 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -6614,6 +6614,257 @@ }; })(); + /* Peter Wuille Taproot Address Validation Functions */ + + // Copyright (c) 2017, 2021 Pieter Wuille + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + // THE SOFTWARE. + + (function(){ + + var bech32 = GLOBAL.bech32 = {}; + var segwit_addr = GLOBAL.segwit_addr = {}; + + var CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; + var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; + + const encodings = { + BECH32: "bech32", + BECH32M: "bech32m", + }; + + + function getEncodingConst (enc) { + if (enc == bech32.encodings.BECH32) { + return 1; + } else if (enc == bech32.encodings.BECH32M) { + return 0x2bc830a3; + } else { + return null; + } + } + + function polymod (values) { + var chk = 1; + for (var p = 0; p < values.length; ++p) { + var top = chk >> 25; + chk = (chk & 0x1ffffff) << 5 ^ values[p]; + for (var i = 0; i < 5; ++i) { + if ((top >> i) & 1) { + chk ^= GENERATOR[i]; + } + } + } + return chk; + } + + function hrpExpand (hrp) { + var ret = []; + var p; + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) >> 5); + } + ret.push(0); + for (p = 0; p < hrp.length; ++p) { + ret.push(hrp.charCodeAt(p) & 31); + } + return ret; + } + + function verifyChecksum (hrp, data, enc) { + return polymod(hrpExpand(hrp).concat(data)) === getEncodingConst(enc); + } + + function createChecksum (hrp, data, enc) { + var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]); + var mod = polymod(values) ^ getEncodingConst(enc); + var ret = []; + for (var p = 0; p < 6; ++p) { + ret.push((mod >> 5 * (5 - p)) & 31); + } + return ret; + } + + function bech32_encode (hrp, data, enc) { + var combined = data.concat(createChecksum(hrp, data, enc)); + var ret = hrp + '1'; + for (var p = 0; p < combined.length; ++p) { + ret += CHARSET.charAt(combined[p]); + } + return ret; + } + + function bech32_decode (bechString, enc) { + var p; + var has_lower = false; + var has_upper = false; + for (p = 0; p < bechString.length; ++p) { + if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) { + return null; + } + if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) { + has_lower = true; + } + if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) { + has_upper = true; + } + } + if (has_lower && has_upper) { + return null; + } + bechString = bechString.toLowerCase(); + var pos = bechString.lastIndexOf('1'); + if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) { + return null; + } + var hrp = bechString.substring(0, pos); + var data = []; + for (p = pos + 1; p < bechString.length; ++p) { + var d = CHARSET.indexOf(bechString.charAt(p)); + if (d === -1) { + return null; + } + data.push(d); + } + if (!verifyChecksum(hrp, data, enc)) { + return null; + } + return {hrp: hrp, data: data.slice(0, data.length - 6)}; + } + + bech32.encodings = encodings; + bech32.encode = bech32_encode; + bech32.decode = bech32_decode; + + // SIPA SEGWIT_ADDR.JS + + // Copyright (c) 2017, 2021 Pieter Wuille + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + // THE SOFTWARE. + + + + + + function convertbits (data, frombits, tobits, pad) { + var acc = 0; + var bits = 0; + var ret = []; + var maxv = (1 << tobits) - 1; + for (var p = 0; p < data.length; ++p) { + var value = data[p]; + if (value < 0 || (value >> frombits) !== 0) { + return null; + } + acc = (acc << frombits) | value; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + ret.push((acc >> bits) & maxv); + } + } + if (pad) { + if (bits > 0) { + ret.push((acc << (tobits - bits)) & maxv); + } + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return null; + } + return ret; + } + + segwit_addr.convertbits = convertbits; + + function segwit_addr_decode (hrp, addr) { + var bech32m = false; + var dec = bech32.decode(addr, bech32.encodings.BECH32); + if (dec === null) { + dec = bech32.decode(addr, bech32.encodings.BECH32M); + bech32m = true; + } + if (dec === null || dec.hrp !== hrp || dec.data.length < 1 || dec.data[0] > 16) { + return null; + } + var res = convertbits(dec.data.slice(1), 5, 8, false); + if (res === null || res.length < 2 || res.length > 40) { + return null; + } + if (dec.data[0] === 0 && res.length !== 20 && res.length !== 32) { + return null; + } + if (dec.data[0] === 0 && bech32m) { + return null; + } + if (dec.data[0] !== 0 && !bech32m) { + return null; + } + return {version: dec.data[0], program: res}; + } + + segwit_addr.decode = segwit_addr_decode; + + function segwit_addr_encode (hrp, version, program) { + var enc = bech32.encodings.BECH32; + if (version > 0) { + enc = bech32.encodings.BECH32M; + } + var ret = bech32.encode(hrp, [version].concat(convertbits(program, 8, 5, true)), enc); + if (segwit_addr_decode(hrp, ret) === null) { + return null; + } + return ret; + } + + segwit_addr.encode = segwit_addr_encode; + + + function isTaprootAddress(address) { + try { + const taprootDecoded = segwit_addr.decode("bc", address); + return taprootDecoded !== null && taprootDecoded.version === 1; + } catch (error) { + return false; + } + } + + segwit_addr.isTaprootAddress = isTaprootAddress; + +} )(); + + + //coin.js (function () { /* @@ -6975,6 +7226,15 @@ /* decode or validate an address and return the hash */ coinjs.addressDecode = function (addr) { try { + //Addition of Taproot check before other checks + if (segwit_addr.isTaprootAddress(addr)){ + var data = segwit_addr.decode("bc",addr); + data.type = "bech32m"; + data.outstring = "5120" + Crypto.util.bytesToHex(data.program); + return data; + } + + //Resuming regular checks var bytes = coinjs.base58decode(addr); var front = bytes.slice(0, bytes.length - 4); var back = bytes.slice(bytes.length - 4); @@ -7705,6 +7965,13 @@ r.spendToScript = function (address) { var addr = coinjs.addressDecode(address); var s = coinjs.script(); + + //Adding Taproot output writing + if (addr.type == "bech32m") { + s.writeBytes(Crypto.util.hexToBytes(addr.outstring)); + return s; + } + if (addr.type == "bech32" || addr.type == "multisigBech32") { s.writeOp(0); s.writeBytes(Crypto.util.hexToBytes(addr.redeemscript)); @@ -9972,4 +10239,4 @@ } })(); -})(typeof global !== "undefined" ? global : window); \ No newline at end of file +})(typeof global !== "undefined" ? global : window);