@stacks/cli
Version:
Stacks command line tool
610 lines • 23.5 kB
JavaScript
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
;