UNPKG

@stacks/stacking

Version:
325 lines 16.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.pox4SignatureMessage = exports.verifyPox4SignatureHash = exports.signPox4SignatureHash = exports.Pox4SignatureTopic = exports.ensureSignerArgsReadiness = exports.ensureLegacyBtcAddressForPox1 = exports.ensurePox2Activated = exports.unwrapMap = exports.unwrap = exports.poxAddressToBtcAddress = exports.poxAddressToTuple = exports.getErrorString = exports.extractPoxAddressFromClarityValue = exports.decodeBtcAddressBytes = exports.decodeBtcAddress = exports.btcAddressVersionToLegacyHashMode = exports.InvalidAddressError = void 0; const sha256_1 = require("@noble/hashes/sha256"); const base_1 = require("@scure/base"); const common_1 = require("@stacks/common"); const encryption_1 = require("@stacks/encryption"); const network_1 = require("@stacks/network"); const transactions_1 = require("@stacks/transactions"); const constants_1 = require("./constants"); class InvalidAddressError extends Error { constructor(address, innerError) { const msg = `'${address}' is not a valid P2PKH/P2SH/P2WPKH/P2WSH/P2TR address`; super(msg); this.message = msg; this.name = this.constructor.name; this.innerError = innerError; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } } exports.InvalidAddressError = InvalidAddressError; function btcAddressVersionToLegacyHashMode(btcAddressVersion) { switch (btcAddressVersion) { case constants_1.BitcoinNetworkVersion.mainnet.P2PKH: return constants_1.PoXAddressVersion.P2PKH; case constants_1.BitcoinNetworkVersion.testnet.P2PKH: return constants_1.PoXAddressVersion.P2PKH; case constants_1.BitcoinNetworkVersion.mainnet.P2SH: return constants_1.PoXAddressVersion.P2SH; case constants_1.BitcoinNetworkVersion.testnet.P2SH: return constants_1.PoXAddressVersion.P2SH; default: throw new Error('Invalid pox address version'); } } exports.btcAddressVersionToLegacyHashMode = btcAddressVersionToLegacyHashMode; function nativeAddressToSegwitVersion(witnessVersion, dataLength) { if (witnessVersion === constants_1.SEGWIT_V0 && dataLength === 20) return constants_1.PoXAddressVersion.P2WPKH; if (witnessVersion === constants_1.SEGWIT_V0 && dataLength === 32) return constants_1.PoXAddressVersion.P2WSH; if (witnessVersion === constants_1.SEGWIT_V1 && dataLength === 32) return constants_1.PoXAddressVersion.P2TR; throw new Error('Invalid native segwit witness version and byte length. Currently, only P2WPKH, P2WSH, and P2TR are supported.'); } function bech32Decode(btcAddress) { const { words: bech32Words } = base_1.bech32.decode(btcAddress); const witnessVersion = bech32Words[0]; if (witnessVersion > 0) throw new Error('Addresses with a witness version >= 1 should be encoded in bech32m'); return { witnessVersion, data: base_1.bech32.fromWords(bech32Words.slice(1)), }; } function bech32MDecode(btcAddress) { const { words: bech32MWords } = base_1.bech32m.decode(btcAddress); const witnessVersion = bech32MWords[0]; if (witnessVersion == 0) throw new Error('Addresses with witness version 1 should be encoded in bech32'); return { witnessVersion, data: base_1.bech32m.fromWords(bech32MWords.slice(1)), }; } function decodeNativeSegwitBtcAddress(btcAddress) { if (constants_1.SEGWIT_V0_ADDR_PREFIX.test(btcAddress)) return bech32Decode(btcAddress); if (constants_1.SEGWIT_V1_ADDR_PREFIX.test(btcAddress)) return bech32MDecode(btcAddress); throw new Error(`Native segwit address ${btcAddress} does not match valid prefix ${constants_1.SEGWIT_V0_ADDR_PREFIX} or ${constants_1.SEGWIT_V1_ADDR_PREFIX}`); } function decodeBtcAddress(btcAddress) { const { version, data } = decodeBtcAddressBytes(btcAddress); return { version, data: (0, common_1.bytesToHex)(data) }; } exports.decodeBtcAddress = decodeBtcAddress; function decodeBtcAddressBytes(btcAddress) { try { if (constants_1.B58_ADDR_PREFIXES.test(btcAddress)) { const b58 = (0, encryption_1.base58CheckDecode)(btcAddress); const addressVersion = btcAddressVersionToLegacyHashMode(b58.version); return { version: addressVersion, data: b58.hash, }; } else if (constants_1.SEGWIT_ADDR_PREFIXES.test(btcAddress)) { const b32 = decodeNativeSegwitBtcAddress(btcAddress); const addressVersion = nativeAddressToSegwitVersion(b32.witnessVersion, b32.data.length); return { version: addressVersion, data: b32.data, }; } throw new Error('Unknown BTC address prefix.'); } catch (error) { throw new InvalidAddressError(btcAddress, error); } } exports.decodeBtcAddressBytes = decodeBtcAddressBytes; function extractPoxAddressFromClarityValue(poxAddrClarityValue) { const clarityValue = poxAddrClarityValue; if (clarityValue.type !== transactions_1.ClarityType.Tuple || !clarityValue.value) { throw new Error('Invalid argument, expected ClarityValue to be a TupleCV'); } if (!('version' in clarityValue.value) || !('hashbytes' in clarityValue.value)) { throw new Error('Invalid argument, expected Clarity tuple value to contain `version` and `hashbytes` keys'); } const versionCV = clarityValue.value['version']; const hashBytesCV = clarityValue.value['hashbytes']; if (versionCV.type !== transactions_1.ClarityType.Buffer || hashBytesCV.type !== transactions_1.ClarityType.Buffer) { throw new Error('Invalid argument, expected Clarity tuple value to contain `version` and `hashbytes` buffers'); } return { version: (0, common_1.hexToBytes)(versionCV.value)[0], hashBytes: (0, common_1.hexToBytes)(hashBytesCV.value), }; } exports.extractPoxAddressFromClarityValue = extractPoxAddressFromClarityValue; function getErrorString(error) { switch (error) { case constants_1.StackingErrors.ERR_STACKING_UNREACHABLE: return 'Stacking unreachable'; case constants_1.StackingErrors.ERR_STACKING_CORRUPTED_STATE: return 'Stacking state is corrupted'; case constants_1.StackingErrors.ERR_STACKING_INSUFFICIENT_FUNDS: return 'Insufficient funds'; case constants_1.StackingErrors.ERR_STACKING_INVALID_LOCK_PERIOD: return 'Invalid lock period'; case constants_1.StackingErrors.ERR_STACKING_ALREADY_STACKED: return 'Account already stacked. Concurrent stacking not allowed.'; case constants_1.StackingErrors.ERR_STACKING_NO_SUCH_PRINCIPAL: return 'Principal does not exist'; case constants_1.StackingErrors.ERR_STACKING_EXPIRED: return 'Stacking expired'; case constants_1.StackingErrors.ERR_STACKING_STX_LOCKED: return 'STX balance is locked'; case constants_1.StackingErrors.ERR_STACKING_PERMISSION_DENIED: return 'Permission denied'; case constants_1.StackingErrors.ERR_STACKING_THRESHOLD_NOT_MET: return 'Stacking threshold not met'; case constants_1.StackingErrors.ERR_STACKING_POX_ADDRESS_IN_USE: return 'PoX address already in use'; case constants_1.StackingErrors.ERR_STACKING_INVALID_POX_ADDRESS: return 'Invalid PoX address'; case constants_1.StackingErrors.ERR_STACKING_ALREADY_REJECTED: return 'Stacking already rejected'; case constants_1.StackingErrors.ERR_STACKING_INVALID_AMOUNT: return 'Invalid amount'; case constants_1.StackingErrors.ERR_NOT_ALLOWED: return 'Stacking not allowed'; case constants_1.StackingErrors.ERR_STACKING_ALREADY_DELEGATED: return 'Already delegated'; case constants_1.StackingErrors.ERR_DELEGATION_EXPIRES_DURING_LOCK: return 'Delegation expires during lock period'; case constants_1.StackingErrors.ERR_DELEGATION_TOO_MUCH_LOCKED: return 'Delegation too much locked'; case constants_1.StackingErrors.ERR_DELEGATION_POX_ADDR_REQUIRED: return 'PoX address required for delegation'; case constants_1.StackingErrors.ERR_INVALID_START_BURN_HEIGHT: return 'Invalid start burn height'; case constants_1.StackingErrors.ERR_NOT_CURRENT_STACKER: return 'ERR_NOT_CURRENT_STACKER'; case constants_1.StackingErrors.ERR_STACK_EXTEND_NOT_LOCKED: return 'Stacker must be currently locked'; case constants_1.StackingErrors.ERR_STACK_INCREASE_NOT_LOCKED: return 'Stacker must be currently locked'; case constants_1.StackingErrors.ERR_DELEGATION_NO_REWARD_SLOT: return 'Invalid reward-cycle and reward-cycle-index'; case constants_1.StackingErrors.ERR_DELEGATION_WRONG_REWARD_SLOT: return 'PoX address must match the one on record'; case constants_1.StackingErrors.ERR_STACKING_IS_DELEGATED: return 'Stacker must be directly stacking and not delegating'; case constants_1.StackingErrors.ERR_STACKING_NOT_DELEGATED: return 'Stacker must be delegating and not be directly stacking'; } } exports.getErrorString = getErrorString; function poxAddressToTuple(poxAddress) { const { version, data } = decodeBtcAddressBytes(poxAddress); const versionBuff = (0, transactions_1.bufferCV)((0, common_1.bigIntToBytes)(BigInt(version), 1)); const hashBuff = (0, transactions_1.bufferCV)(data); return (0, transactions_1.tupleCV)({ version: versionBuff, hashbytes: hashBuff, }); } exports.poxAddressToTuple = poxAddressToTuple; function legacyHashModeToBtcAddressVersion(hashMode, network) { switch (hashMode) { case constants_1.PoXAddressVersion.P2PKH: return constants_1.BitcoinNetworkVersion[network].P2PKH; case constants_1.PoXAddressVersion.P2SH: case constants_1.PoXAddressVersion.P2SHP2WPKH: case constants_1.PoXAddressVersion.P2SHP2WSH: return constants_1.BitcoinNetworkVersion[network].P2SH; default: throw new Error('Invalid pox address version'); } } function _poxAddressToBtcAddress_Values(version, hash, network) { if (!network_1.StacksNetworks.includes(network)) throw new Error('Invalid network.'); if (typeof hash === 'string') hash = (0, common_1.hexToBytes)(hash); switch (version) { case constants_1.PoXAddressVersion.P2PKH: case constants_1.PoXAddressVersion.P2SH: case constants_1.PoXAddressVersion.P2SHP2WPKH: case constants_1.PoXAddressVersion.P2SHP2WSH: { const btcAddrVersion = legacyHashModeToBtcAddressVersion(version, network); return (0, encryption_1.base58CheckEncode)(btcAddrVersion, hash); } case constants_1.PoXAddressVersion.P2WPKH: case constants_1.PoXAddressVersion.P2WSH: { const words = base_1.bech32.toWords(hash); return base_1.bech32.encode(constants_1.SegwitPrefix[network], [constants_1.SEGWIT_V0, ...words]); } case constants_1.PoXAddressVersion.P2TR: { const words = base_1.bech32m.toWords(hash); return base_1.bech32m.encode(constants_1.SegwitPrefix[network], [constants_1.SEGWIT_V1, ...words]); } } throw new Error(`Unexpected address version: ${version}`); } function _poxAddressToBtcAddress_ClarityValue(poxAddrClarityValue, network) { const poxAddr = extractPoxAddressFromClarityValue(poxAddrClarityValue); return _poxAddressToBtcAddress_Values(poxAddr.version, poxAddr.hashBytes, network); } function poxAddressToBtcAddress(...args) { if (typeof args[0] === 'number') return _poxAddressToBtcAddress_Values(args[0], args[1], args[2]); return _poxAddressToBtcAddress_ClarityValue(args[0], args[1]); } exports.poxAddressToBtcAddress = poxAddressToBtcAddress; function unwrap(optional) { if (optional.type === transactions_1.ClarityType.OptionalSome) return optional.value; if (optional.type === transactions_1.ClarityType.OptionalNone) return undefined; throw new Error("Object is not an 'Optional'"); } exports.unwrap = unwrap; function unwrapMap(optional, map) { if (optional.type === transactions_1.ClarityType.OptionalSome) return map(optional.value); if (optional.type === transactions_1.ClarityType.OptionalNone) return undefined; throw new Error("Object is not an 'Optional'"); } exports.unwrapMap = unwrapMap; function ensurePox2Activated(operationInfo) { if (operationInfo.period === constants_1.PoxOperationPeriod.Period1) throw new Error(`PoX-2 has not activated yet (currently in period ${operationInfo.period} of PoX-2 operation)`); } exports.ensurePox2Activated = ensurePox2Activated; function ensureLegacyBtcAddressForPox1({ contract, poxAddress, }) { if (!poxAddress) return; if (contract.endsWith('.pox') && !constants_1.B58_ADDR_PREFIXES.test(poxAddress)) { throw new Error('PoX-1 requires P2PKH/P2SH/P2SH-P2WPKH/P2SH-P2WSH bitcoin addresses'); } } exports.ensureLegacyBtcAddressForPox1 = ensureLegacyBtcAddressForPox1; function ensureSignerArgsReadiness({ contract, signerKey, signerSignature, maxAmount, authId, }) { const hasMaxAmount = typeof maxAmount !== 'undefined'; const hasAuthId = typeof authId !== 'undefined'; if (/\.pox(-[2-3])?$/.test(contract)) { if (signerKey || signerSignature || hasMaxAmount || hasAuthId) { throw new Error('PoX-1, PoX-2 and PoX-3 do not accept a `signerKey`, `signerSignature`, `maxAmount` or `authId`'); } } else { if (!signerKey || !hasMaxAmount || typeof authId === 'undefined') { throw new Error('PoX-4 requires a `signerKey` (buff 33), `maxAmount` (uint), and `authId` (uint)'); } } } exports.ensureSignerArgsReadiness = ensureSignerArgsReadiness; var Pox4SignatureTopic; (function (Pox4SignatureTopic) { Pox4SignatureTopic["StackStx"] = "stack-stx"; Pox4SignatureTopic["AggregateCommit"] = "agg-commit"; Pox4SignatureTopic["AggregateIncrease"] = "agg-increase"; Pox4SignatureTopic["StackExtend"] = "stack-extend"; Pox4SignatureTopic["StackIncrease"] = "stack-increase"; })(Pox4SignatureTopic || (exports.Pox4SignatureTopic = Pox4SignatureTopic = {})); function signPox4SignatureHash({ topic, poxAddress, rewardCycle, period, network, privateKey, maxAmount, authId, }) { return (0, transactions_1.signStructuredData)({ ...pox4SignatureMessage({ topic, poxAddress, rewardCycle, period, network, maxAmount, authId }), privateKey, }); } exports.signPox4SignatureHash = signPox4SignatureHash; function verifyPox4SignatureHash({ topic, poxAddress, rewardCycle, period, network, publicKey, signature, maxAmount, authId, }) { return (0, encryption_1.verifyMessageSignatureRsv)({ message: (0, sha256_1.sha256)((0, transactions_1.encodeStructuredDataBytes)(pox4SignatureMessage({ topic, poxAddress, rewardCycle, period, network, maxAmount, authId }))), publicKey, signature, }); } exports.verifyPox4SignatureHash = verifyPox4SignatureHash; function pox4SignatureMessage({ topic, poxAddress, rewardCycle, period: lockPeriod, network: networkOrName, maxAmount, authId, }) { const network = (0, network_1.networkFrom)(networkOrName); const message = (0, transactions_1.tupleCV)({ 'pox-addr': poxAddressToTuple(poxAddress), 'reward-cycle': (0, transactions_1.uintCV)(rewardCycle), topic: (0, transactions_1.stringAsciiCV)(topic), period: (0, transactions_1.uintCV)(lockPeriod), 'max-amount': (0, transactions_1.uintCV)(maxAmount), 'auth-id': (0, transactions_1.uintCV)(authId), }); const domain = (0, transactions_1.tupleCV)({ name: (0, transactions_1.stringAsciiCV)('pox-4-signer'), version: (0, transactions_1.stringAsciiCV)('1.0.0'), 'chain-id': (0, transactions_1.uintCV)(network.chainId), }); return { message, domain }; } exports.pox4SignatureMessage = pox4SignatureMessage; //# sourceMappingURL=utils.js.map