@secux/app-btc
Version:
SecuX Hardware Wallet BTC API
18 lines (15 loc) • 9.59 kB
JavaScript
;
/*!
Copyright 2022 SecuX Technology Inc
Copyright Chen Wei-En
Copyright Wu Tsung-Yu
Licensed under the Apache License, Version 2.0 (the License);
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an AS IS BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/Object.defineProperty(exports,"__esModule",{value:!0}),exports.logger=exports.Hash160=exports.CoinType=exports.PaymentBTC=void 0;const bech32_1=require("bech32"),hash_js_1=require("hash.js"),interface_1=require("./interface");Object.defineProperty(exports,"CoinType",{enumerable:!0,get:function(){return interface_1.CoinType}});const bs58_1=require("@secux/utility/lib/bs58"),utility_1=require("@secux/utility"),utils_1=require("./utils");exports.logger=null===utility_1.Logger||void 0===utility_1.Logger?void 0:utility_1.Logger.child({id:"payment"});class PaymentBTC{static CoinSupported(coin){if(coin===interface_1.CoinType.BITCOINCASH)throw Error("Please use class PaymentBCH instead");if(coin===interface_1.CoinType.GROESTL)throw Error("Please use class PaymentGRS instead")}static p2pkh(coin,opt){if(this.CoinSupported(coin),!opt.publickey&&!opt.hash)throw Error("Invalid Parameters");if(opt.publickey&&opt.hash)throw Error("Invalid Parameters");const pkHash=opt.hash?opt.hash:Hash160(opt.publickey);null===exports.logger||void 0===exports.logger||exports.logger.info(`publickey hash: ${pkHash.toString("hex")}`);const network=interface_1.coinmap[coin],address=this.bs58check.encode(pkHash,Buffer.from([network.pubKeyHash])),op=Buffer.from([interface_1.OPCODES.OP_DUP,interface_1.OPCODES.OP_HASH160,20]),check=Buffer.from([interface_1.OPCODES.OP_EQUALVERIFY,interface_1.OPCODES.OP_CHECKSIG]);return{address,scriptPublickey:Buffer.concat([op,pkHash,check])}}static p2sh(coin,opt){if(this.CoinSupported(coin),!opt.redeemScript&&!opt.hash)throw Error("Invalid Parameters");const redeemHash=opt.hash?opt.hash:Hash160(opt.redeemScript);null===exports.logger||void 0===exports.logger||exports.logger.info(`redeem hash: ${redeemHash.toString("hex")}`);const network=interface_1.coinmap[coin],address=this.bs58check.encode(redeemHash,Buffer.from([network.scriptHash])),op=Buffer.from([interface_1.OPCODES.OP_HASH160,20]),check=Buffer.from([interface_1.OPCODES.OP_EQUAL]);return{address,scriptPublickey:Buffer.concat([op,redeemHash,check])}}static p2wpkh(coin,opt){if(this.CoinSupported(coin),!opt.publickey&&!opt.hash)throw Error("Invalid Parameters");if(opt.publickey&&opt.hash)throw Error("Invalid Parameters");const pkHash=opt.hash?opt.hash:Hash160(opt.publickey);null===exports.logger||void 0===exports.logger||exports.logger.info(`publickey hash: ${pkHash.toString("hex")}`);let network=interface_1.coinmap[coin];const words=bech32_1.bech32.toWords(pkHash);words.unshift(0);const address=bech32_1.bech32.encode(network.bech32,words),op=Buffer.from([interface_1.OPCODES.OP_0,20]);return{address,scriptPublickey:Buffer.concat([op,pkHash])}}static p2wsh(coin,opt){if(this.CoinSupported(coin),!opt.redeemScript&&!opt.hash)throw Error("Invalid Parameters");const redeemHash=opt.hash?opt.hash:Sha256(opt.redeemScript);null===exports.logger||void 0===exports.logger||exports.logger.info(`redeem hash: ${redeemHash.toString("hex")}`);let network=interface_1.coinmap[coin];const words=bech32_1.bech32.toWords(redeemHash);words.unshift(0);const address=bech32_1.bech32.encode(network.bech32,words),op=Buffer.from([interface_1.OPCODES.OP_0,32]);return{address,scriptPublickey:Buffer.concat([op,redeemHash])}}static p2ms(m,publickeys){if(m<=0)throw Error('Invalid paramter "m"');if(m>15)throw Error("you can use up to 15 public keys");m+=interface_1.OPCODES.OP_INT_BASE;const n=publickeys.length+interface_1.OPCODES.OP_INT_BASE,multi_pk=Buffer.concat(publickeys.map((pk=>Buffer.from([pk.length,...pk])))),scriptPubicKey=Buffer.concat([Buffer.from([m]),multi_pk,Buffer.from([n]),Buffer.from([interface_1.OPCODES.OP_CHECKMULTISIG])]);return null===exports.logger||void 0===exports.logger||exports.logger.info(`scriptPublickey: ${scriptPubicKey.toString("hex")}`),{scriptPubicKey}}static p2tr(coin,opt){if(this.CoinSupported(coin),!opt.publickey&&!opt.hash)throw Error("Invalid Parameters");if(opt.publickey&&opt.hash)throw Error("Invalid Parameters");let tweaked=opt.hash;void 0===tweaked&&(tweaked=(0,utils_1.toTweakedPublickey)(opt.publickey));const network=interface_1.coinmap[coin],words=bech32_1.bech32.toWords(tweaked);words.unshift(1);const address=bech32_1.bech32m.encode(network.bech32,words),header=Buffer.from([81,32]);return{address,scriptPublickey:Buffer.concat([header,tweaked])}}static decode(coin,address){const network=interface_1.coinmap[coin];if(network.bech32&&RegExp(`^${network.bech32}`,"i").test(address)){let result;if("p"===address.slice(network.bech32.length+1)[0])result=bech32_1.bech32m.decode(address);else result=bech32_1.bech32.decode(address);const version=result.words.shift();switch(version){case 0:const hash160=Buffer.from(bech32_1.bech32.fromWords(result.words));if(null===exports.logger||void 0===exports.logger||exports.logger.debug(`bech32 address: ${address}\nbech32 decoded: ${hash160.toString("hex")}`),20==hash160.length)return this.p2wpkh(coin,{hash:hash160}).scriptPublickey;if(32==hash160.length)return this.p2wsh(coin,{hash:hash160}).scriptPublickey;case 1:const tweaked=Buffer.from(bech32_1.bech32m.fromWords(result.words));return null===exports.logger||void 0===exports.logger||exports.logger.debug(`bech32m address: ${address}\nbech32m decoded: ${tweaked.toString("hex")}`),this.p2tr(coin,{hash:tweaked}).scriptPublickey;default:throw Error(`ArgumentError: unsupported witness version, got "${version}" from address "${address}"`)}}try{const hash160=this.bs58check.decode(address),prefix=hash160[0],hash=hash160.slice(1);if(prefix===network.scriptHash)return this.p2sh(coin,{hash}).scriptPublickey;if(prefix===network.pubKeyHash)return this.p2pkh(coin,{hash}).scriptPublickey}catch(error){null===exports.logger||void 0===exports.logger||exports.logger.debug(`${error.toString()}, cointype: ${interface_1.CoinType[coin]}, address: ${address}`)}throw Error(`ArgumentError: invalid address for ${interface_1.CoinType[coin]}, got ${address}`)}static encode(coin,script){const network=interface_1.coinmap[coin];switch(this.classify(script)){case interface_1.ScriptType.P2PKH:{const pkHash=script.slice(3,23);return this.bs58check.encode(pkHash,Buffer.from([network.pubKeyHash]))}case interface_1.ScriptType.P2SH:case interface_1.ScriptType.P2SH_P2PKH:case interface_1.ScriptType.P2SH_P2WPKH:case interface_1.ScriptType.P2SH_P2MS:{const redeemHash=script.slice(2,22);return this.bs58check.encode(redeemHash,Buffer.from([network.scriptHash]))}case interface_1.ScriptType.P2WPKH:case interface_1.ScriptType.P2WSH:{const hash=script.slice(2),words=bech32_1.bech32.toWords(hash);return words.unshift(0),bech32_1.bech32.encode(network.bech32,words)}case interface_1.ScriptType.P2TR:{const tweaked=script.slice(2),version=script[0]-80,words=bech32_1.bech32.toWords(tweaked);return words.unshift(version),bech32_1.bech32m.encode(network.bech32,words)}}throw Error(`not supported script: ${script.toString("hex")}`)}static classify(script){if(this.isP2WPKH(script))return interface_1.ScriptType.P2WPKH;if(this.isP2PKH(script))return interface_1.ScriptType.P2PKH;if(this.isP2TR(script))return interface_1.ScriptType.P2TR;if(this.isP2MS(script))return interface_1.ScriptType.P2MS;if(this.isP2WSH(script))return interface_1.ScriptType.P2WSH;if(this.isP2SH(script))return interface_1.ScriptType.P2SH;throw Error(`non-standard script: ${script.toString("hex")}`)}static isP2PKH(script){return 25===script.length&&script[0]===interface_1.OPCODES.OP_DUP&&script[1]===interface_1.OPCODES.OP_HASH160&&20===script[2]&&script[23]===interface_1.OPCODES.OP_EQUALVERIFY&&script[24]===interface_1.OPCODES.OP_CHECKSIG}static isP2SH(script){return 23===script.length&&script[0]===interface_1.OPCODES.OP_HASH160&&20===script[1]&&script[22]===interface_1.OPCODES.OP_EQUAL}static isP2WPKH(script){return 22===script.length&&script[0]===interface_1.OPCODES.OP_0&&20===script[1]}static isP2WSH(script){return 34===script.length&&script[0]===interface_1.OPCODES.OP_0&&32===script[1]}static isP2TR(script){return 34===script.length&&81===script[0]&&32===script[1]}static isP2MS(script){if(script[script.length-1]!==interface_1.OPCODES.OP_CHECKMULTISIG)return!1;const m=script[1]-interface_1.OPCODES.OP_INT_BASE,n=script[script.length-2]-interface_1.OPCODES.OP_INT_BASE;if(m>n)return!1;let validLength=1;for(let i=0;i<n;i++){validLength+=script[validLength]+1}return validLength+2===script.length}static isArbitraryData(script){if(script.length>83||script[0]!==interface_1.OPCODES.OP_RETURN)return!1;const dataLength=script[1];return script.length===dataLength+2&&(null===exports.logger||void 0===exports.logger||exports.logger.debug(`abitrary data: ${script.toString("hex")}`),!0)}}function Sha256(data){const sha=(0,hash_js_1.sha256)().update(data).digest();return Buffer.from(sha)}function Hash160(publickey){const sha=Sha256(publickey);return Buffer.from((0,hash_js_1.ripemd160)().update(sha).digest())}exports.PaymentBTC=PaymentBTC,PaymentBTC.bs58check=new bs58_1.bs58Check((function(data){const sha1=Sha256(data);return Sha256(sha1)})),exports.Hash160=Hash160;