UNPKG

@stacks/cli

Version:
610 lines • 23.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.subdomainOpToZFPieces = exports.isTestnetAddress = exports.generateExplorerTxPageUrl = exports.answerToClarityValue = exports.parseClarityFunctionArgAnswers = exports.argToPrompt = exports.makePromptsFromArgList = exports.getIDAppKeys = exports.getOwnerKeyFromIDAddress = exports.getIDAddress = exports.mkdirs = exports.getBackupPhrase = exports.getpass = exports.nameLookup = exports.getNameInfoEasy = exports.makeProfileJWT = exports.hash160 = exports.canonicalPrivateKey = exports.getPublicKeyFromPrivateKey = exports.JSONStringify = exports.decodePrivateKey = exports.parseSegwitP2SHKeys = exports.parseMultiSigKeys = exports.parseNullSigner = exports.hasKeys = exports.SegwitP2SHKeySigner = exports.MultiSigKeySigner = exports.NullSigner = void 0; const logger = __importStar(require("winston")); const bitcoinjs = __importStar(require("bitcoinjs-lib")); const readline = __importStar(require("readline")); const stream = __importStar(require("stream")); const fs = __importStar(require("fs")); const blockstack = __importStar(require("blockstack")); const transactions_1 = require("@stacks/transactions"); const network_1 = require("@stacks/network"); const ZoneFile = require('zone-file'); const argparse_1 = require("./argparse"); const common_1 = require("./common"); const encrypt_1 = require("./encrypt"); const keys_1 = require("./keys"); class NullSigner extends common_1.CLITransactionSigner { } exports.NullSigner = NullSigner; class MultiSigKeySigner extends common_1.CLITransactionSigner { constructor(redeemScript, privateKeys) { super(); this.redeemScript = Buffer.from(redeemScript, 'hex'); this.privateKeys = privateKeys; this.isComplete = true; try { const chunks = bitcoinjs.script.decompile(this.redeemScript); const firstOp = chunks[0]; this.m = parseInt(bitcoinjs.script.toASM([firstOp]).slice(3), 10); this.address = bitcoinjs.address.toBase58Check(bitcoinjs.crypto.hash160(this.redeemScript), blockstack.config.network.layer1.scriptHash); } catch (e) { logger.error(e); throw new Error('Improper redeem script for multi-sig input.'); } } getAddress() { return Promise.resolve().then(() => this.address); } signTransaction(txIn, signingIndex) { return Promise.resolve().then(() => { const keysToUse = this.privateKeys.slice(0, this.m); keysToUse.forEach(keyHex => { const ecPair = blockstack.hexStringToECPair(keyHex); txIn.sign(signingIndex, ecPair, this.redeemScript); }); }); } signerVersion() { return 0; } } exports.MultiSigKeySigner = MultiSigKeySigner; class SegwitP2SHKeySigner extends common_1.CLITransactionSigner { constructor(redeemScript, witnessScript, m, privateKeys) { super(); this.redeemScript = Buffer.from(redeemScript, 'hex'); this.witnessScript = Buffer.from(witnessScript, 'hex'); this.address = bitcoinjs.address.toBase58Check(bitcoinjs.crypto.hash160(this.redeemScript), blockstack.config.network.layer1.scriptHash); this.privateKeys = privateKeys; this.m = m; this.isComplete = true; } getAddress() { return Promise.resolve().then(() => this.address); } findUTXO(txIn, signingIndex, utxos) { const private_tx = txIn.__TX; const txidBuf = new Buffer(private_tx.ins[signingIndex].hash.slice()); const outpoint = private_tx.ins[signingIndex].index; txidBuf.reverse(); const txid = txidBuf.toString('hex'); for (let i = 0; i < utxos.length; i++) { if (utxos[i].tx_hash === txid && utxos[i].tx_output_n === outpoint) { if (!utxos[i].value) { throw new Error(`UTXO for hash=${txid} vout=${outpoint} has no value`); } return utxos[i]; } } throw new Error(`No UTXO for input hash=${txid} vout=${outpoint}`); } signTransaction(txIn, signingIndex) { return Promise.resolve() .then(() => { return this.getAddress(); }) .then(address => { return blockstack.config.network.getUTXOs(address); }) .then(utxos => { const utxo = this.findUTXO(txIn, signingIndex, utxos); if (this.m === 1) { const ecPair = blockstack.hexStringToECPair(this.privateKeys[0]); txIn.sign(signingIndex, ecPair, this.redeemScript, undefined, utxo.value); } else { const keysToUse = this.privateKeys.slice(0, this.m); keysToUse.forEach(keyHex => { const ecPair = blockstack.hexStringToECPair(keyHex); txIn.sign(signingIndex, ecPair, this.redeemScript, undefined, utxo.value, this.witnessScript); }); } }); } signerVersion() { return 0; } } exports.SegwitP2SHKeySigner = SegwitP2SHKeySigner; function hasKeys(signer) { if ((0, common_1.isCLITransactionSigner)(signer)) { const s = signer; return s.isComplete; } else { return true; } } exports.hasKeys = hasKeys; function parseNullSigner(addrString) { if (!addrString.startsWith('nosign:')) { throw new Error('Invalid nosign string'); } const addr = addrString.slice('nosign:'.length); return new NullSigner(addr); } exports.parseNullSigner = parseNullSigner; function parseMultiSigKeys(serializedPrivateKeys) { const matches = serializedPrivateKeys.match(argparse_1.PRIVATE_KEY_MULTISIG_PATTERN); if (!matches) { throw new Error('Invalid multisig private key string'); } const m = parseInt(matches[1]); const parts = serializedPrivateKeys.split(','); const privkeys = []; for (let i = 1; i < 256; i++) { const pk = parts[i]; if (!pk) { break; } if (!pk.match(argparse_1.PRIVATE_KEY_PATTERN)) { throw new Error('Invalid private key string'); } privkeys.push(pk); } const pubkeys = privkeys.map(pk => { return Buffer.from(getPublicKeyFromPrivateKey(pk), 'hex'); }); const multisigInfo = bitcoinjs.payments.p2ms({ m, pubkeys }); return new MultiSigKeySigner(multisigInfo.output.toString('hex'), privkeys); } exports.parseMultiSigKeys = parseMultiSigKeys; function parseSegwitP2SHKeys(serializedPrivateKeys) { const matches = serializedPrivateKeys.match(argparse_1.PRIVATE_KEY_SEGWIT_P2SH_PATTERN); if (!matches) { throw new Error('Invalid segwit p2sh private key string'); } const m = parseInt(matches[1]); const parts = serializedPrivateKeys.split(','); const privkeys = []; for (let i = 1; i < 256; i++) { const pk = parts[i]; if (!pk) { break; } if (!pk.match(argparse_1.PRIVATE_KEY_PATTERN)) { throw new Error('Invalid private key string'); } privkeys.push(pk); } const pubkeys = privkeys.map(pk => { return Buffer.from(getPublicKeyFromPrivateKey(pk), 'hex'); }); let redeemScript; let witnessScript = ''; if (m === 1) { const p2wpkh = bitcoinjs.payments.p2wpkh({ pubkey: pubkeys[0] }); const p2sh = bitcoinjs.payments.p2sh({ redeem: p2wpkh }); redeemScript = p2sh.redeem.output.toString('hex'); } else { const p2ms = bitcoinjs.payments.p2ms({ m, pubkeys }); const p2wsh = bitcoinjs.payments.p2wsh({ redeem: p2ms }); const p2sh = bitcoinjs.payments.p2sh({ redeem: p2wsh }); redeemScript = p2sh.redeem.output.toString('hex'); witnessScript = p2wsh.redeem.output.toString('hex'); } return new SegwitP2SHKeySigner(redeemScript, witnessScript, m, privkeys); } exports.parseSegwitP2SHKeys = parseSegwitP2SHKeys; function decodePrivateKey(serializedPrivateKey) { const nosignMatches = serializedPrivateKey.match(argparse_1.PRIVATE_KEY_NOSIGN_PATTERN); if (!!nosignMatches) { return parseNullSigner(serializedPrivateKey); } const singleKeyMatches = serializedPrivateKey.match(argparse_1.PRIVATE_KEY_PATTERN); if (!!singleKeyMatches) { return serializedPrivateKey; } const multiKeyMatches = serializedPrivateKey.match(argparse_1.PRIVATE_KEY_MULTISIG_PATTERN); if (!!multiKeyMatches) { return parseMultiSigKeys(serializedPrivateKey); } const segwitP2SHMatches = serializedPrivateKey.match(argparse_1.PRIVATE_KEY_SEGWIT_P2SH_PATTERN); if (!!segwitP2SHMatches) { return parseSegwitP2SHKeys(serializedPrivateKey); } throw new Error('Unparseable private key'); } exports.decodePrivateKey = decodePrivateKey; function JSONStringify(obj, stderr = false) { if ((!stderr && process.stdout.isTTY) || (stderr && process.stderr.isTTY)) { return JSON.stringify(obj, null, 2); } return JSON.stringify(obj); } exports.JSONStringify = JSONStringify; function getPublicKeyFromPrivateKey(privateKey) { const ecKeyPair = blockstack.hexStringToECPair(privateKey); return ecKeyPair.publicKey.toString('hex'); } exports.getPublicKeyFromPrivateKey = getPublicKeyFromPrivateKey; function canonicalPrivateKey(privkey) { if (privkey.length == 66 && privkey.slice(-2) === '01') { return privkey.substring(0, 64); } return privkey; } exports.canonicalPrivateKey = canonicalPrivateKey; function hash160(buff) { return bitcoinjs.crypto.hash160(buff); } exports.hash160 = hash160; function makeProfileJWT(profileData, privateKey) { const signedToken = blockstack.signProfileToken(profileData, privateKey); const wrappedToken = blockstack.wrapProfileToken(signedToken); const tokenRecords = [wrappedToken]; return JSONStringify(tokenRecords); } exports.makeProfileJWT = makeProfileJWT; function getNameInfoEasy(network, name) { const nameInfoPromise = network .getNameInfo(name) .then((nameInfo) => nameInfo) .catch((error) => { if (error.message === 'Name not found') { return null; } else { throw error; } }); return nameInfoPromise; } exports.getNameInfoEasy = getNameInfoEasy; async function nameLookup(network, name, includeProfile = true) { const nameInfoPromise = getNameInfoEasy(network, name); const profilePromise = includeProfile ? blockstack.lookupProfile(name).catch(() => null) : Promise.resolve().then(() => null); const zonefilePromise = nameInfoPromise.then((nameInfo) => nameInfo ? nameInfo.zonefile : null); const [profile, zonefile, nameInfo] = await Promise.all([ profilePromise, zonefilePromise, nameInfoPromise, ]); let profileObj = profile; if (!nameInfo) { throw new Error('Name not found'); } if (nameInfo.hasOwnProperty('grace_period') && nameInfo.grace_period) { throw new Error(`Name is expired at block ${nameInfo.expire_block} ` + `and must be renewed by block ${nameInfo.renewal_deadline}`); } let profileUrl = null; try { const zonefileJSON = ZoneFile.parseZoneFile(zonefile); if (zonefileJSON.uri && zonefileJSON.hasOwnProperty('$origin')) { profileUrl = blockstack.getTokenFileUrl(zonefileJSON); } } catch (e) { profileObj = null; } const ret = { zonefile: zonefile, profile: profileObj, profileUrl: profileUrl, }; return ret; } exports.nameLookup = nameLookup; function getpass(promptStr, cb) { const silentOutput = new stream.Writable({ write: (_chunk, _encoding, callback) => { callback(); }, }); const rl = readline.createInterface({ input: process.stdin, output: silentOutput, terminal: true, }); process.stderr.write(promptStr); rl.question('', passwd => { rl.close(); process.stderr.write('\n'); cb(passwd); }); return; } exports.getpass = getpass; async function getBackupPhrase(backupPhraseOrCiphertext, password) { if (backupPhraseOrCiphertext.split(/ +/g).length > 1) { return backupPhraseOrCiphertext; } else { const pass = await new Promise((resolve, reject) => { if (!process.stdin.isTTY && !password) { reject(new Error('Password argument required in non-interactive mode')); } else if (process.env.password) { resolve(process.env.password); } else { getpass('Enter password: ', p => { resolve(p); }); } }); return await (0, encrypt_1.decryptBackupPhrase)(Buffer.from(backupPhraseOrCiphertext, 'base64'), pass); } } exports.getBackupPhrase = getBackupPhrase; function mkdirs(path) { if (path.length === 0 || path[0] !== '/') { throw new Error('Path must be absolute'); } const pathParts = path.replace(/^\//, '').split('/'); let tmpPath = '/'; for (let i = 0; i <= pathParts.length; i++) { try { const statInfo = fs.lstatSync(tmpPath); if ((statInfo.mode & fs.constants.S_IFDIR) === 0) { throw new Error(`Not a directory: ${tmpPath}`); } } catch (e) { if (e.code === 'ENOENT') { fs.mkdirSync(tmpPath); } else { throw e; } } if (i === pathParts.length) { break; } tmpPath = `${tmpPath}/${pathParts[i]}`; } } exports.mkdirs = mkdirs; async function getIDAddress(network, nameOrIDAddress) { if (nameOrIDAddress.match(argparse_1.ID_ADDRESS_PATTERN)) { return nameOrIDAddress; } else { const nameInfo = await network.getNameInfo(nameOrIDAddress); return `ID-${nameInfo.address}`; } } exports.getIDAddress = getIDAddress; async function getOwnerKeyFromIDAddress(network, mnemonic, idAddress) { let index = 0; while (true) { const keyInfo = await (0, keys_1.getOwnerKeyInfo)(network, mnemonic, index); if (keyInfo.idAddress === idAddress) { return keyInfo.privateKey; } index++; } } exports.getOwnerKeyFromIDAddress = getOwnerKeyFromIDAddress; async function getIDAppKeys(network, nameOrIDAddress, appOrigin, mnemonicOrCiphertext) { const mnemonic = await getBackupPhrase(mnemonicOrCiphertext); const idAddress = await getIDAddress(network, nameOrIDAddress); const appKeyInfo = await (0, keys_1.getApplicationKeyInfo)(network, mnemonic, idAddress, appOrigin); const appPrivateKey = (0, keys_1.extractAppKey)(network, appKeyInfo); const ownerPrivateKey = await getOwnerKeyFromIDAddress(network, mnemonic, idAddress); const ret = { appPrivateKey, ownerPrivateKey, mnemonic, }; return ret; } exports.getIDAppKeys = getIDAppKeys; function makePromptsFromArgList(expectedArgs) { const prompts = []; for (let i = 0; i < expectedArgs.length; i++) { prompts.push(argToPrompt(expectedArgs[i])); } return prompts; } exports.makePromptsFromArgList = makePromptsFromArgList; function argToPrompt(arg) { const name = arg.name; const type = arg.type; const typeString = (0, transactions_1.getTypeString)(type); if ((0, transactions_1.isClarityAbiPrimitive)(type)) { if (type === 'uint128') { return { type: 'input', name, message: `Enter value for function argument "${name}" of type ${typeString}`, }; } else if (type === 'int128') { return { type: 'input', name, message: `Enter value for function argument "${name}" of type ${typeString}`, }; } else if (type === 'bool') { return { type: 'list', name, message: `Enter value for function argument "${name}" of type ${typeString}`, choices: ['True', 'False'], }; } else if (type === 'principal') { return { type: 'input', name, message: `Enter value for function argument "${name}" of type ${typeString}`, }; } else { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } } else if ((0, transactions_1.isClarityAbiBuffer)(type)) { return { type: 'input', name, message: `Enter value for function argument "${name}" of type ${typeString}`, }; } else if ((0, transactions_1.isClarityAbiStringAscii)(type)) { return { type: 'input', name, message: `Enter value for function argument "${name}" of type ${typeString}`, }; } else if ((0, transactions_1.isClarityAbiStringUtf8)(type)) { return { type: 'input', name, message: `Enter value for function argument "${name}" of type ${typeString}`, }; } else if ((0, transactions_1.isClarityAbiResponse)(type)) { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } else if ((0, transactions_1.isClarityAbiOptional)(type)) { return { type: 'input', name, message: `Enter value for function argument "${name}" of type ${typeString}`, }; } else if ((0, transactions_1.isClarityAbiTuple)(type)) { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } else if ((0, transactions_1.isClarityAbiList)(type)) { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } else { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } } exports.argToPrompt = argToPrompt; function parseClarityFunctionArgAnswers(answers, expectedArgs) { const functionArgs = []; for (let i = 0; i < expectedArgs.length; i++) { const expectedArg = expectedArgs[i]; const answer = answers[expectedArg.name]; functionArgs.push(answerToClarityValue(answer, expectedArg)); } return functionArgs; } exports.parseClarityFunctionArgAnswers = parseClarityFunctionArgAnswers; function answerToClarityValue(answer, arg) { const type = arg.type; const typeString = (0, transactions_1.getTypeString)(type); if ((0, transactions_1.isClarityAbiPrimitive)(type)) { if (type === 'uint128') { return (0, transactions_1.uintCV)(answer); } else if (type === 'int128') { return (0, transactions_1.intCV)(answer); } else if (type === 'bool') { return answer == 'True' ? (0, transactions_1.trueCV)() : (0, transactions_1.falseCV)(); } else if (type === 'principal') { return (0, transactions_1.standardPrincipalCV)(answer); } else { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } } else if ((0, transactions_1.isClarityAbiBuffer)(type)) { return (0, transactions_1.bufferCVFromString)(answer); } else if ((0, transactions_1.isClarityAbiStringAscii)(type)) { return (0, transactions_1.stringAsciiCV)(answer); } else if ((0, transactions_1.isClarityAbiStringUtf8)(type)) { return (0, transactions_1.stringUtf8CV)(answer); } else if ((0, transactions_1.isClarityAbiResponse)(type)) { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } else if ((0, transactions_1.isClarityAbiOptional)(type)) { return (0, transactions_1.someCV)(answerToClarityValue(answer, { name: arg.name, type: type.optional })); } else if ((0, transactions_1.isClarityAbiTuple)(type)) { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } else if ((0, transactions_1.isClarityAbiList)(type)) { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } else { throw new Error(`Contract function contains unsupported Clarity ABI type: ${typeString}`); } } exports.answerToClarityValue = answerToClarityValue; function generateExplorerTxPageUrl(txid, network) { if (network.transactionVersion === network_1.TransactionVersion.Testnet) { return `https://explorer.hiro.so/txid/0x${txid}?chain=testnet`; } else { return `https://explorer.hiro.so/txid/0x${txid}?chain=mainnet`; } } exports.generateExplorerTxPageUrl = generateExplorerTxPageUrl; function isTestnetAddress(address) { const addressInfo = bitcoinjs.address.fromBase58Check(address); return addressInfo.version === bitcoinjs.networks.testnet.pubKeyHash; } exports.isTestnetAddress = isTestnetAddress; function destructZonefile(zonefile) { const encodedZonefile = Buffer.from(zonefile).toString('base64'); const pieces = 1 + Math.floor(encodedZonefile.length / 250); const destructed = []; for (let i = 0; i < pieces; i++) { const startIndex = i * 250; const currentPiece = encodedZonefile.slice(startIndex, startIndex + 250); if (currentPiece.length > 0) { destructed.push(currentPiece); } } return destructed; } function subdomainOpToZFPieces(operation) { const destructedZonefile = destructZonefile(operation.zonefile); const txt = [ `owner=${operation.owner}`, `seqn=${operation.sequenceNumber}`, `parts=${destructedZonefile.length}`, ]; destructedZonefile.forEach((zfPart, ix) => txt.push(`zf${ix}=${zfPart}`)); if (operation.signature) { txt.push(`sig=${operation.signature}`); } return { name: operation.subdomainName, txt, }; } exports.subdomainOpToZFPieces = subdomainOpToZFPieces; //# sourceMappingURL=utils.js.map