@stacks/transactions
Advanced tools
Comparing version 6.15.1-pr.516f0cae.3 to 6.15.1-pr.56e21ff8.15
@@ -36,3 +36,2 @@ import { IntegerType } from '@stacks/common'; | ||
export declare function createMultiSigSpendingCondition(hashMode: MultiSigHashMode, numSigs: number, pubKeys: string[], nonce: IntegerType, fee: IntegerType): MultiSigSpendingCondition; | ||
export declare function isSingleSig(condition: SpendingConditionOpts): condition is SingleSigSpendingConditionOpts; | ||
export declare function serializeSingleSigSpendingCondition(condition: SingleSigSpendingConditionOpts): Uint8Array; | ||
@@ -39,0 +38,0 @@ export declare function serializeMultiSigSpendingCondition(condition: MultiSigSpendingConditionOpts): Uint8Array; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.deserializeAuthorization = exports.serializeAuthorization = exports.setSponsor = exports.setSponsorNonce = exports.setNonce = exports.getFee = exports.setFee = exports.verifyOrigin = exports.intoInitialSighashAuth = exports.createSponsoredAuth = exports.createStandardAuth = exports.nextVerification = exports.nextSignature = exports.makeSigHashPreSign = exports.deserializeSpendingCondition = exports.serializeSpendingCondition = exports.deserializeMultiSigSpendingCondition = exports.deserializeSingleSigSpendingCondition = exports.serializeMultiSigSpendingCondition = exports.serializeSingleSigSpendingCondition = exports.isSingleSig = exports.createMultiSigSpendingCondition = exports.createSingleSigSpendingCondition = exports.emptyMessageSignature = void 0; | ||
exports.deserializeAuthorization = exports.serializeAuthorization = exports.setSponsor = exports.setSponsorNonce = exports.setNonce = exports.getFee = exports.setFee = exports.verifyOrigin = exports.intoInitialSighashAuth = exports.createSponsoredAuth = exports.createStandardAuth = exports.nextVerification = exports.nextSignature = exports.makeSigHashPreSign = exports.deserializeSpendingCondition = exports.serializeSpendingCondition = exports.deserializeMultiSigSpendingCondition = exports.deserializeSingleSigSpendingCondition = exports.serializeMultiSigSpendingCondition = exports.serializeSingleSigSpendingCondition = exports.isNonSequentialMultiSig = exports.isSequentialMultiSig = exports.isSingleSig = exports.createMultiSigSpendingCondition = exports.createSingleSigSpendingCondition = exports.emptyMessageSignature = void 0; | ||
const common_1 = require("@stacks/common"); | ||
@@ -50,2 +50,11 @@ const constants_1 = require("./constants"); | ||
exports.isSingleSig = isSingleSig; | ||
function isSequentialMultiSig(hashMode) { | ||
return hashMode === constants_1.AddressHashMode.SerializeP2SH || hashMode === constants_1.AddressHashMode.SerializeP2WSH; | ||
} | ||
exports.isSequentialMultiSig = isSequentialMultiSig; | ||
function isNonSequentialMultiSig(hashMode) { | ||
return (hashMode === constants_1.AddressHashMode.SerializeP2SHNonSequential || | ||
hashMode === constants_1.AddressHashMode.SerializeP2WSHNonSequential); | ||
} | ||
exports.isNonSequentialMultiSig = isNonSequentialMultiSig; | ||
function clearCondition(condition) { | ||
@@ -139,4 +148,7 @@ const cloned = (0, utils_1.cloneDeep)(condition); | ||
const signaturesRequired = bytesReader.readUInt16BE(); | ||
if (haveUncompressed && hashMode === constants_1.AddressHashMode.SerializeP2SH) | ||
if (haveUncompressed && | ||
(hashMode === constants_1.AddressHashMode.SerializeP2WSH || | ||
hashMode === constants_1.AddressHashMode.SerializeP2WSHNonSequential)) { | ||
throw new errors_1.VerificationError('Uncompressed keys are not allowed in this hash mode'); | ||
} | ||
return { | ||
@@ -244,3 +256,2 @@ hashMode, | ||
for (const field of condition.fields) { | ||
let foundPubKey; | ||
switch (field.contents.type) { | ||
@@ -250,3 +261,3 @@ case constants_1.StacksMessageType.PublicKey: | ||
haveUncompressed = true; | ||
foundPubKey = field.contents; | ||
publicKeys.push(field.contents); | ||
break; | ||
@@ -257,4 +268,6 @@ case constants_1.StacksMessageType.MessageSignature: | ||
const { pubKey, nextSigHash } = nextVerification(curSigHash, authType, condition.fee, condition.nonce, field.pubKeyEncoding, field.contents); | ||
curSigHash = nextSigHash; | ||
foundPubKey = pubKey; | ||
if (isSequentialMultiSig(condition.hashMode)) { | ||
curSigHash = nextSigHash; | ||
} | ||
publicKeys.push(pubKey); | ||
numSigs += 1; | ||
@@ -265,7 +278,9 @@ if (numSigs === 65536) | ||
} | ||
publicKeys.push(foundPubKey); | ||
} | ||
if (numSigs !== condition.signaturesRequired) | ||
if ((isSequentialMultiSig(condition.hashMode) && numSigs !== condition.signaturesRequired) || | ||
(isNonSequentialMultiSig(condition.hashMode) && numSigs < condition.signaturesRequired)) | ||
throw new errors_1.VerificationError('Incorrect number of signatures'); | ||
if (haveUncompressed && condition.hashMode === constants_1.AddressHashMode.SerializeP2SH) | ||
if (haveUncompressed && | ||
(condition.hashMode === constants_1.AddressHashMode.SerializeP2WSH || | ||
condition.hashMode === constants_1.AddressHashMode.SerializeP2WSHNonSequential)) | ||
throw new errors_1.VerificationError('Uncompressed keys are not allowed in this hash mode'); | ||
@@ -272,0 +287,0 @@ const addrBytes = (0, types_1.addressFromPublicKeys)(0, condition.hashMode, condition.signaturesRequired, publicKeys).hash160; |
@@ -217,2 +217,11 @@ import { IntegerType } from '@stacks/common'; | ||
} | ||
export interface UnsignedMultiSigOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
address?: string; | ||
useNonSequentialMultiSig?: boolean; | ||
} | ||
export type SignedMultiSigOptions = UnsignedMultiSigOptions & { | ||
signerKeys: string[]; | ||
}; | ||
export interface TokenTransferOptions { | ||
@@ -234,11 +243,4 @@ recipient: string | PrincipalCV; | ||
} | ||
export interface UnsignedMultiSigTokenTransferOptions extends TokenTransferOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export interface SignedMultiSigTokenTransferOptions extends TokenTransferOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type UnsignedMultiSigTokenTransferOptions = TokenTransferOptions & UnsignedMultiSigOptions; | ||
export type SignedMultiSigTokenTransferOptions = TokenTransferOptions & SignedMultiSigOptions; | ||
export declare function makeUnsignedSTXTokenTransfer(txOptions: UnsignedTokenTransferOptions | UnsignedMultiSigTokenTransferOptions): Promise<StacksTransaction>; | ||
@@ -266,11 +268,4 @@ export declare function makeSTXTokenTransfer(txOptions: SignedTokenTransferOptions | SignedMultiSigTokenTransferOptions): Promise<StacksTransaction>; | ||
} | ||
export interface UnsignedMultiSigContractDeployOptions extends BaseContractDeployOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export interface SignedMultiSigContractDeployOptions extends BaseContractDeployOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type UnsignedMultiSigContractDeployOptions = BaseContractDeployOptions & UnsignedMultiSigOptions; | ||
export type SignedMultiSigContractDeployOptions = BaseContractDeployOptions & SignedMultiSigOptions; | ||
export declare function estimateContractDeploy(transaction: StacksTransaction, network?: StacksNetworkName | StacksNetwork): Promise<bigint>; | ||
@@ -300,11 +295,4 @@ export declare function makeContractDeploy(txOptions: SignedContractDeployOptions | SignedMultiSigContractDeployOptions): Promise<StacksTransaction>; | ||
} | ||
export interface UnsignedMultiSigContractCallOptions extends ContractCallOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export interface SignedMultiSigContractCallOptions extends ContractCallOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type UnsignedMultiSigContractCallOptions = ContractCallOptions & UnsignedMultiSigOptions; | ||
export type SignedMultiSigContractCallOptions = ContractCallOptions & SignedMultiSigOptions; | ||
export declare function estimateContractFunctionCall(transaction: StacksTransaction, network?: StacksNetworkName | StacksNetwork): Promise<bigint>; | ||
@@ -311,0 +299,0 @@ export declare function makeUnsignedContractCall(txOptions: UnsignedContractCallOptions | UnsignedMultiSigContractCallOptions): Promise<StacksTransaction>; |
@@ -20,5 +20,15 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
async function _getNonceApi(address, network) { | ||
const url = `${network.coreApiUrl}/extended/v1/address/${address}/nonces`; | ||
const response = await network.fetchFn(url); | ||
const result = await response.json(); | ||
return BigInt(result.possible_next_nonce); | ||
} | ||
async function getNonce(address, network) { | ||
const derivedNetwork = network_1.StacksNetwork.fromNameOrNetwork(network ?? new network_1.StacksMainnet()); | ||
const url = derivedNetwork.getAccountApiUrl(address); | ||
try { | ||
return await _getNonceApi(address, derivedNetwork); | ||
} | ||
catch (e) { } | ||
const response = await derivedNetwork.fetchFn(url); | ||
@@ -173,3 +183,9 @@ if (!response.ok) { | ||
else { | ||
spendingCondition = (0, authorization_1.createMultiSigSpendingCondition)(constants_1.AddressHashMode.SerializeP2SH, options.numSignatures, options.publicKeys, options.nonce, options.fee); | ||
const hashMode = options.useNonSequentialMultiSig | ||
? constants_1.AddressHashMode.SerializeP2SHNonSequential | ||
: constants_1.AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder(options.publicKeys, options.numSignatures, hashMode, (0, postcondition_types_1.createAddress)(options.address).hash160) | ||
: options.publicKeys; | ||
spendingCondition = (0, authorization_1.createMultiSigSpendingCondition)(hashMode, options.numSignatures, publicKeys, options.nonce, options.fee); | ||
} | ||
@@ -212,12 +228,3 @@ if (options.sponsored) { | ||
const transaction = await makeUnsignedSTXTokenTransfer(options); | ||
const signer = new signer_1.TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = (0, keys_1.pubKeyfromPrivKey)(key); | ||
pubKeys = pubKeys.filter(pk => pk !== (0, common_1.bytesToHex)(pubKey.data)); | ||
signer.signOrigin((0, keys_1.createStacksPrivateKey)(key)); | ||
} | ||
for (const key of pubKeys) { | ||
signer.appendOrigin((0, keys_1.publicKeyFromBytes)((0, common_1.hexToBytes)(key))); | ||
} | ||
mutatingSignAppendMultiSig(transaction, txOptions.publicKeys.slice(), txOptions.signerKeys, txOptions.address); | ||
return transaction; | ||
@@ -265,12 +272,3 @@ } | ||
const transaction = await makeUnsignedContractDeploy(options); | ||
const signer = new signer_1.TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = (0, keys_1.pubKeyfromPrivKey)(key); | ||
pubKeys = pubKeys.filter(pk => pk !== (0, common_1.bytesToHex)(pubKey.data)); | ||
signer.signOrigin((0, keys_1.createStacksPrivateKey)(key)); | ||
} | ||
for (const key of pubKeys) { | ||
signer.appendOrigin((0, keys_1.publicKeyFromBytes)((0, common_1.hexToBytes)(key))); | ||
} | ||
mutatingSignAppendMultiSig(transaction, txOptions.publicKeys.slice(), txOptions.signerKeys, txOptions.address); | ||
return transaction; | ||
@@ -297,3 +295,9 @@ } | ||
else { | ||
spendingCondition = (0, authorization_1.createMultiSigSpendingCondition)(constants_1.AddressHashMode.SerializeP2SH, options.numSignatures, options.publicKeys, options.nonce, options.fee); | ||
const hashMode = options.useNonSequentialMultiSig | ||
? constants_1.AddressHashMode.SerializeP2SHNonSequential | ||
: constants_1.AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder(options.publicKeys, options.numSignatures, hashMode, (0, postcondition_types_1.createAddress)(options.address).hash160) | ||
: options.publicKeys; | ||
spendingCondition = (0, authorization_1.createMultiSigSpendingCondition)(hashMode, options.numSignatures, publicKeys, options.nonce, options.fee); | ||
} | ||
@@ -385,3 +389,9 @@ if (options.sponsored) { | ||
else { | ||
spendingCondition = (0, authorization_1.createMultiSigSpendingCondition)(constants_1.AddressHashMode.SerializeP2SH, options.numSignatures, options.publicKeys, options.nonce, options.fee); | ||
const hashMode = options.useNonSequentialMultiSig | ||
? constants_1.AddressHashMode.SerializeP2SHNonSequential | ||
: constants_1.AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder(options.publicKeys, options.numSignatures, hashMode, (0, postcondition_types_1.createAddress)(options.address).hash160) | ||
: options.publicKeys; | ||
spendingCondition = (0, authorization_1.createMultiSigSpendingCondition)(hashMode, options.numSignatures, publicKeys, options.nonce, options.fee); | ||
} | ||
@@ -431,12 +441,3 @@ if (options.sponsored) { | ||
const transaction = await makeUnsignedContractCall(options); | ||
const signer = new signer_1.TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = (0, keys_1.pubKeyfromPrivKey)(key); | ||
pubKeys = pubKeys.filter(pk => pk !== (0, common_1.bytesToHex)(pubKey.data)); | ||
signer.signOrigin((0, keys_1.createStacksPrivateKey)(key)); | ||
} | ||
for (const key of pubKeys) { | ||
signer.appendOrigin((0, keys_1.publicKeyFromBytes)((0, common_1.hexToBytes)(key))); | ||
} | ||
mutatingSignAppendMultiSig(transaction, txOptions.publicKeys.slice(), txOptions.signerKeys, txOptions.address); | ||
return transaction; | ||
@@ -613,2 +614,30 @@ } | ||
exports.estimateTransactionFeeWithFallback = estimateTransactionFeeWithFallback; | ||
function mutatingSignAppendMultiSig(transaction, publicKeys, signerKeys, address) { | ||
if ((0, authorization_1.isSingleSig)(transaction.auth.spendingCondition)) { | ||
throw new Error('Transaction is not a multi-sig transaction'); | ||
} | ||
const signer = new signer_1.TransactionSigner(transaction); | ||
const pubs = ensureValidMultiSigPublicKeyOrder(publicKeys, transaction.auth.spendingCondition.signaturesRequired, transaction.auth.spendingCondition.hashMode, address ? (0, postcondition_types_1.createAddress)(address).hash160 : undefined); | ||
for (const publicKey of pubs) { | ||
const signerKey = signerKeys.find(key => (0, common_1.bytesToHex)((0, keys_1.pubKeyfromPrivKey)(key).data) === publicKey); | ||
if (signerKey) { | ||
signer.signOrigin((0, keys_1.createStacksPrivateKey)(signerKey)); | ||
} | ||
else { | ||
signer.appendOrigin((0, keys_1.publicKeyFromBytes)((0, common_1.hexToBytes)(publicKey))); | ||
} | ||
} | ||
} | ||
function ensureValidMultiSigPublicKeyOrder(publicKeys, numSigs, hashMode, hash) { | ||
if (!hash) | ||
return publicKeys; | ||
const hashUnsorted = (0, types_1.addressFromPublicKeys)(0, hashMode, numSigs, publicKeys.map(keys_1.createStacksPublicKey)).hash160; | ||
if (hashUnsorted === hash) | ||
return publicKeys; | ||
const publicKeysSorted = publicKeys.slice().sort(); | ||
const hashSorted = (0, types_1.addressFromPublicKeys)(0, hashMode, numSigs, publicKeysSorted.map(keys_1.createStacksPublicKey)).hash160; | ||
if (hashSorted === hash) | ||
return publicKeysSorted; | ||
throw new Error('Failed to find matching multi-sig address given public-keys.'); | ||
} | ||
//# sourceMappingURL=builders.js.map |
import { contractPrincipalCV, deserializeCV, listCV, noneCV, responseErrorCV, responseOkCV, serializeCV, someCV, standardPrincipalCV, tupleCV } from './clarity'; | ||
export { prettyPrint } from './clarity/prettyPrint'; | ||
export { prettyPrint, stringify } from './clarity/prettyPrint'; | ||
export { parse } from './clarity/parser'; | ||
export declare const bool: (bool: boolean) => import("./clarity/types/booleanCV").BooleanCV; | ||
@@ -4,0 +5,0 @@ export declare const int: (value: import("@stacks/common").IntegerType) => import("./clarity/types/intCV").IntCV; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.deserialize = exports.serialize = exports.tuple = exports.error = exports.ok = exports.some = exports.none = exports.bufferFromUtf8 = exports.bufferFromAscii = exports.bufferFromHex = exports.buffer = exports.stringUtf8 = exports.stringAscii = exports.list = exports.standardPrincipal = exports.contractPrincipal = exports.address = exports.principal = exports.uint = exports.int = exports.bool = exports.prettyPrint = void 0; | ||
exports.deserialize = exports.serialize = exports.tuple = exports.error = exports.ok = exports.some = exports.none = exports.bufferFromUtf8 = exports.bufferFromAscii = exports.bufferFromHex = exports.buffer = exports.stringUtf8 = exports.stringAscii = exports.list = exports.standardPrincipal = exports.contractPrincipal = exports.address = exports.principal = exports.uint = exports.int = exports.bool = exports.parse = exports.stringify = exports.prettyPrint = void 0; | ||
const common_1 = require("@stacks/common"); | ||
@@ -8,2 +8,5 @@ const clarity_1 = require("./clarity"); | ||
Object.defineProperty(exports, "prettyPrint", { enumerable: true, get: function () { return prettyPrint_1.prettyPrint; } }); | ||
Object.defineProperty(exports, "stringify", { enumerable: true, get: function () { return prettyPrint_1.stringify; } }); | ||
var parser_1 = require("./clarity/parser"); | ||
Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parser_1.parse; } }); | ||
exports.bool = clarity_1.boolCV; | ||
@@ -10,0 +13,0 @@ exports.int = clarity_1.intCV; |
import { ClarityValue } from '.'; | ||
export declare function prettyPrint(cv: ClarityValue, space?: number): string; | ||
export declare function stringify(cv: ClarityValue, space?: number): string; | ||
export declare const prettyPrint: typeof stringify; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.prettyPrint = void 0; | ||
exports.prettyPrint = exports.stringify = void 0; | ||
const common_1 = require("@stacks/common"); | ||
@@ -68,6 +68,7 @@ const _1 = require("."); | ||
} | ||
function prettyPrint(cv, space = 0) { | ||
function stringify(cv, space = 0) { | ||
return prettyPrintWithDepth(cv, space, 0); | ||
} | ||
exports.prettyPrint = prettyPrint; | ||
exports.stringify = stringify; | ||
exports.prettyPrint = stringify; | ||
//# sourceMappingURL=prettyPrint.js.map |
@@ -30,4 +30,6 @@ "use strict"; | ||
case constants_1.AddressHashMode.SerializeP2SH: | ||
case constants_1.AddressHashMode.SerializeP2SHNonSequential: | ||
case constants_1.AddressHashMode.SerializeP2WPKH: | ||
case constants_1.AddressHashMode.SerializeP2WSH: | ||
case constants_1.AddressHashMode.SerializeP2WSHNonSequential: | ||
switch (txVersion) { | ||
@@ -34,0 +36,0 @@ case constants_1.TransactionVersion.Mainnet: |
@@ -77,6 +77,8 @@ export declare enum ChainID { | ||
SerializeP2WPKH = 2, | ||
SerializeP2WSH = 3 | ||
SerializeP2WSH = 3, | ||
SerializeP2SHNonSequential = 5, | ||
SerializeP2WSHNonSequential = 7 | ||
} | ||
export type SingleSigHashMode = AddressHashMode.SerializeP2PKH | AddressHashMode.SerializeP2WPKH; | ||
export type MultiSigHashMode = AddressHashMode.SerializeP2SH | AddressHashMode.SerializeP2WSH; | ||
export type MultiSigHashMode = AddressHashMode.SerializeP2SH | AddressHashMode.SerializeP2WSH | AddressHashMode.SerializeP2SHNonSequential | AddressHashMode.SerializeP2WSHNonSequential; | ||
export declare enum AddressVersion { | ||
@@ -83,0 +85,0 @@ MainnetSingleSig = 22, |
@@ -105,2 +105,4 @@ "use strict"; | ||
AddressHashMode[AddressHashMode["SerializeP2WSH"] = 3] = "SerializeP2WSH"; | ||
AddressHashMode[AddressHashMode["SerializeP2SHNonSequential"] = 5] = "SerializeP2SHNonSequential"; | ||
AddressHashMode[AddressHashMode["SerializeP2WSHNonSequential"] = 7] = "SerializeP2WSHNonSequential"; | ||
})(AddressHashMode || (exports.AddressHashMode = AddressHashMode = {})); | ||
@@ -107,0 +109,0 @@ var AddressVersion; |
@@ -36,3 +36,2 @@ import { IntegerType } from '@stacks/common'; | ||
export declare function createMultiSigSpendingCondition(hashMode: MultiSigHashMode, numSigs: number, pubKeys: string[], nonce: IntegerType, fee: IntegerType): MultiSigSpendingCondition; | ||
export declare function isSingleSig(condition: SpendingConditionOpts): condition is SingleSigSpendingConditionOpts; | ||
export declare function serializeSingleSigSpendingCondition(condition: SingleSigSpendingConditionOpts): Uint8Array; | ||
@@ -39,0 +38,0 @@ export declare function serializeMultiSigSpendingCondition(condition: MultiSigSpendingConditionOpts): Uint8Array; |
@@ -43,2 +43,9 @@ import { bytesToHex, concatArray, hexToBytes, intToBigInt, intToBytes, writeUInt16BE, } from '@stacks/common'; | ||
} | ||
export function isSequentialMultiSig(hashMode) { | ||
return hashMode === AddressHashMode.SerializeP2SH || hashMode === AddressHashMode.SerializeP2WSH; | ||
} | ||
export function isNonSequentialMultiSig(hashMode) { | ||
return (hashMode === AddressHashMode.SerializeP2SHNonSequential || | ||
hashMode === AddressHashMode.SerializeP2WSHNonSequential); | ||
} | ||
function clearCondition(condition) { | ||
@@ -129,4 +136,7 @@ const cloned = cloneDeep(condition); | ||
const signaturesRequired = bytesReader.readUInt16BE(); | ||
if (haveUncompressed && hashMode === AddressHashMode.SerializeP2SH) | ||
if (haveUncompressed && | ||
(hashMode === AddressHashMode.SerializeP2WSH || | ||
hashMode === AddressHashMode.SerializeP2WSHNonSequential)) { | ||
throw new VerificationError('Uncompressed keys are not allowed in this hash mode'); | ||
} | ||
return { | ||
@@ -228,3 +238,2 @@ hashMode, | ||
for (const field of condition.fields) { | ||
let foundPubKey; | ||
switch (field.contents.type) { | ||
@@ -234,3 +243,3 @@ case StacksMessageType.PublicKey: | ||
haveUncompressed = true; | ||
foundPubKey = field.contents; | ||
publicKeys.push(field.contents); | ||
break; | ||
@@ -241,4 +250,6 @@ case StacksMessageType.MessageSignature: | ||
const { pubKey, nextSigHash } = nextVerification(curSigHash, authType, condition.fee, condition.nonce, field.pubKeyEncoding, field.contents); | ||
curSigHash = nextSigHash; | ||
foundPubKey = pubKey; | ||
if (isSequentialMultiSig(condition.hashMode)) { | ||
curSigHash = nextSigHash; | ||
} | ||
publicKeys.push(pubKey); | ||
numSigs += 1; | ||
@@ -249,7 +260,9 @@ if (numSigs === 65536) | ||
} | ||
publicKeys.push(foundPubKey); | ||
} | ||
if (numSigs !== condition.signaturesRequired) | ||
if ((isSequentialMultiSig(condition.hashMode) && numSigs !== condition.signaturesRequired) || | ||
(isNonSequentialMultiSig(condition.hashMode) && numSigs < condition.signaturesRequired)) | ||
throw new VerificationError('Incorrect number of signatures'); | ||
if (haveUncompressed && condition.hashMode === AddressHashMode.SerializeP2SH) | ||
if (haveUncompressed && | ||
(condition.hashMode === AddressHashMode.SerializeP2WSH || | ||
condition.hashMode === AddressHashMode.SerializeP2WSHNonSequential)) | ||
throw new VerificationError('Uncompressed keys are not allowed in this hash mode'); | ||
@@ -256,0 +269,0 @@ const addrBytes = addressFromPublicKeys(0, condition.hashMode, condition.signaturesRequired, publicKeys).hash160; |
@@ -217,2 +217,11 @@ import { IntegerType } from '@stacks/common'; | ||
} | ||
export interface UnsignedMultiSigOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
address?: string; | ||
useNonSequentialMultiSig?: boolean; | ||
} | ||
export type SignedMultiSigOptions = UnsignedMultiSigOptions & { | ||
signerKeys: string[]; | ||
}; | ||
export interface TokenTransferOptions { | ||
@@ -234,11 +243,4 @@ recipient: string | PrincipalCV; | ||
} | ||
export interface UnsignedMultiSigTokenTransferOptions extends TokenTransferOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export interface SignedMultiSigTokenTransferOptions extends TokenTransferOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type UnsignedMultiSigTokenTransferOptions = TokenTransferOptions & UnsignedMultiSigOptions; | ||
export type SignedMultiSigTokenTransferOptions = TokenTransferOptions & SignedMultiSigOptions; | ||
export declare function makeUnsignedSTXTokenTransfer(txOptions: UnsignedTokenTransferOptions | UnsignedMultiSigTokenTransferOptions): Promise<StacksTransaction>; | ||
@@ -266,11 +268,4 @@ export declare function makeSTXTokenTransfer(txOptions: SignedTokenTransferOptions | SignedMultiSigTokenTransferOptions): Promise<StacksTransaction>; | ||
} | ||
export interface UnsignedMultiSigContractDeployOptions extends BaseContractDeployOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export interface SignedMultiSigContractDeployOptions extends BaseContractDeployOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type UnsignedMultiSigContractDeployOptions = BaseContractDeployOptions & UnsignedMultiSigOptions; | ||
export type SignedMultiSigContractDeployOptions = BaseContractDeployOptions & SignedMultiSigOptions; | ||
export declare function estimateContractDeploy(transaction: StacksTransaction, network?: StacksNetworkName | StacksNetwork): Promise<bigint>; | ||
@@ -300,11 +295,4 @@ export declare function makeContractDeploy(txOptions: SignedContractDeployOptions | SignedMultiSigContractDeployOptions): Promise<StacksTransaction>; | ||
} | ||
export interface UnsignedMultiSigContractCallOptions extends ContractCallOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export interface SignedMultiSigContractCallOptions extends ContractCallOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type UnsignedMultiSigContractCallOptions = ContractCallOptions & UnsignedMultiSigOptions; | ||
export type SignedMultiSigContractCallOptions = ContractCallOptions & SignedMultiSigOptions; | ||
export declare function estimateContractFunctionCall(transaction: StacksTransaction, network?: StacksNetworkName | StacksNetwork): Promise<bigint>; | ||
@@ -311,0 +299,0 @@ export declare function makeUnsignedContractCall(txOptions: UnsignedContractCallOptions | UnsignedMultiSigContractCallOptions): Promise<StacksTransaction>; |
import { bytesToHex, hexToBytes, intToBigInt } from '@stacks/common'; | ||
import { StacksNetwork, StacksMainnet, StacksTestnet, createFetchFn, } from '@stacks/network'; | ||
import { c32address } from 'c32check'; | ||
import { createMultiSigSpendingCondition, createSingleSigSpendingCondition, createSponsoredAuth, createStandardAuth, } from './authorization'; | ||
import { createMultiSigSpendingCondition, createSingleSigSpendingCondition, createSponsoredAuth, createStandardAuth, isSingleSig, } from './authorization'; | ||
import { deserializeCV, serializeCV } from './clarity'; | ||
@@ -9,13 +9,23 @@ import { AddressHashMode, AddressVersion, PayloadType, PostConditionMode, TransactionVersion, RECOVERABLE_ECDSA_SIG_LENGTH_BYTES, StacksMessageType, ClarityVersion, } from './constants'; | ||
import { NoEstimateAvailableError } from './errors'; | ||
import { createStacksPrivateKey, getPublicKey, pubKeyfromPrivKey, publicKeyFromBytes, publicKeyToAddress, publicKeyToString, } from './keys'; | ||
import { createStacksPrivateKey, createStacksPublicKey, getPublicKey, pubKeyfromPrivKey, publicKeyFromBytes, publicKeyToAddress, publicKeyToString, } from './keys'; | ||
import { createContractCallPayload, createSmartContractPayload, createTokenTransferPayload, serializePayload, } from './payload'; | ||
import { createFungiblePostCondition, createNonFungiblePostCondition, createSTXPostCondition, } from './postcondition'; | ||
import { createContractPrincipal, createStandardPrincipal, } from './postcondition-types'; | ||
import { createAddress, createContractPrincipal, createStandardPrincipal, } from './postcondition-types'; | ||
import { TransactionSigner } from './signer'; | ||
import { StacksTransaction } from './transaction'; | ||
import { createLPList } from './types'; | ||
import { addressFromPublicKeys, createLPList } from './types'; | ||
import { cvToHex, omit, parseReadOnlyResponse, validateTxId } from './utils'; | ||
async function _getNonceApi(address, network) { | ||
const url = `${network.coreApiUrl}/extended/v1/address/${address}/nonces`; | ||
const response = await network.fetchFn(url); | ||
const result = await response.json(); | ||
return BigInt(result.possible_next_nonce); | ||
} | ||
export async function getNonce(address, network) { | ||
const derivedNetwork = StacksNetwork.fromNameOrNetwork(network ?? new StacksMainnet()); | ||
const url = derivedNetwork.getAccountApiUrl(address); | ||
try { | ||
return await _getNonceApi(address, derivedNetwork); | ||
} | ||
catch (e) { } | ||
const response = await derivedNetwork.fetchFn(url); | ||
@@ -163,3 +173,9 @@ if (!response.ok) { | ||
else { | ||
spendingCondition = createMultiSigSpendingCondition(AddressHashMode.SerializeP2SH, options.numSignatures, options.publicKeys, options.nonce, options.fee); | ||
const hashMode = options.useNonSequentialMultiSig | ||
? AddressHashMode.SerializeP2SHNonSequential | ||
: AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder(options.publicKeys, options.numSignatures, hashMode, createAddress(options.address).hash160) | ||
: options.publicKeys; | ||
spendingCondition = createMultiSigSpendingCondition(hashMode, options.numSignatures, publicKeys, options.nonce, options.fee); | ||
} | ||
@@ -201,12 +217,3 @@ if (options.sponsored) { | ||
const transaction = await makeUnsignedSTXTokenTransfer(options); | ||
const signer = new TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = pubKeyfromPrivKey(key); | ||
pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); | ||
signer.signOrigin(createStacksPrivateKey(key)); | ||
} | ||
for (const key of pubKeys) { | ||
signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); | ||
} | ||
mutatingSignAppendMultiSig(transaction, txOptions.publicKeys.slice(), txOptions.signerKeys, txOptions.address); | ||
return transaction; | ||
@@ -252,12 +259,3 @@ } | ||
const transaction = await makeUnsignedContractDeploy(options); | ||
const signer = new TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = pubKeyfromPrivKey(key); | ||
pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); | ||
signer.signOrigin(createStacksPrivateKey(key)); | ||
} | ||
for (const key of pubKeys) { | ||
signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); | ||
} | ||
mutatingSignAppendMultiSig(transaction, txOptions.publicKeys.slice(), txOptions.signerKeys, txOptions.address); | ||
return transaction; | ||
@@ -283,3 +281,9 @@ } | ||
else { | ||
spendingCondition = createMultiSigSpendingCondition(AddressHashMode.SerializeP2SH, options.numSignatures, options.publicKeys, options.nonce, options.fee); | ||
const hashMode = options.useNonSequentialMultiSig | ||
? AddressHashMode.SerializeP2SHNonSequential | ||
: AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder(options.publicKeys, options.numSignatures, hashMode, createAddress(options.address).hash160) | ||
: options.publicKeys; | ||
spendingCondition = createMultiSigSpendingCondition(hashMode, options.numSignatures, publicKeys, options.nonce, options.fee); | ||
} | ||
@@ -369,3 +373,9 @@ if (options.sponsored) { | ||
else { | ||
spendingCondition = createMultiSigSpendingCondition(AddressHashMode.SerializeP2SH, options.numSignatures, options.publicKeys, options.nonce, options.fee); | ||
const hashMode = options.useNonSequentialMultiSig | ||
? AddressHashMode.SerializeP2SHNonSequential | ||
: AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder(options.publicKeys, options.numSignatures, hashMode, createAddress(options.address).hash160) | ||
: options.publicKeys; | ||
spendingCondition = createMultiSigSpendingCondition(hashMode, options.numSignatures, publicKeys, options.nonce, options.fee); | ||
} | ||
@@ -414,12 +424,3 @@ if (options.sponsored) { | ||
const transaction = await makeUnsignedContractCall(options); | ||
const signer = new TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = pubKeyfromPrivKey(key); | ||
pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); | ||
signer.signOrigin(createStacksPrivateKey(key)); | ||
} | ||
for (const key of pubKeys) { | ||
signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); | ||
} | ||
mutatingSignAppendMultiSig(transaction, txOptions.publicKeys.slice(), txOptions.signerKeys, txOptions.address); | ||
return transaction; | ||
@@ -584,2 +585,30 @@ } | ||
} | ||
function mutatingSignAppendMultiSig(transaction, publicKeys, signerKeys, address) { | ||
if (isSingleSig(transaction.auth.spendingCondition)) { | ||
throw new Error('Transaction is not a multi-sig transaction'); | ||
} | ||
const signer = new TransactionSigner(transaction); | ||
const pubs = ensureValidMultiSigPublicKeyOrder(publicKeys, transaction.auth.spendingCondition.signaturesRequired, transaction.auth.spendingCondition.hashMode, address ? createAddress(address).hash160 : undefined); | ||
for (const publicKey of pubs) { | ||
const signerKey = signerKeys.find(key => bytesToHex(pubKeyfromPrivKey(key).data) === publicKey); | ||
if (signerKey) { | ||
signer.signOrigin(createStacksPrivateKey(signerKey)); | ||
} | ||
else { | ||
signer.appendOrigin(publicKeyFromBytes(hexToBytes(publicKey))); | ||
} | ||
} | ||
} | ||
function ensureValidMultiSigPublicKeyOrder(publicKeys, numSigs, hashMode, hash) { | ||
if (!hash) | ||
return publicKeys; | ||
const hashUnsorted = addressFromPublicKeys(0, hashMode, numSigs, publicKeys.map(createStacksPublicKey)).hash160; | ||
if (hashUnsorted === hash) | ||
return publicKeys; | ||
const publicKeysSorted = publicKeys.slice().sort(); | ||
const hashSorted = addressFromPublicKeys(0, hashMode, numSigs, publicKeysSorted.map(createStacksPublicKey)).hash160; | ||
if (hashSorted === hash) | ||
return publicKeysSorted; | ||
throw new Error('Failed to find matching multi-sig address given public-keys.'); | ||
} | ||
//# sourceMappingURL=builders.js.map |
import { contractPrincipalCV, deserializeCV, listCV, noneCV, responseErrorCV, responseOkCV, serializeCV, someCV, standardPrincipalCV, tupleCV } from './clarity'; | ||
export { prettyPrint } from './clarity/prettyPrint'; | ||
export { prettyPrint, stringify } from './clarity/prettyPrint'; | ||
export { parse } from './clarity/parser'; | ||
export declare const bool: (bool: boolean) => import("./clarity/types/booleanCV").BooleanCV; | ||
@@ -4,0 +5,0 @@ export declare const int: (value: import("@stacks/common").IntegerType) => import("./clarity/types/intCV").IntCV; |
import { asciiToBytes, hexToBytes, utf8ToBytes } from '@stacks/common'; | ||
import { boolCV, bufferCV, contractPrincipalCV, deserializeCV, intCV, listCV, noneCV, responseErrorCV, responseOkCV, serializeCV, someCV, standardPrincipalCV, stringAsciiCV, stringUtf8CV, tupleCV, uintCV, } from './clarity'; | ||
export { prettyPrint } from './clarity/prettyPrint'; | ||
export { prettyPrint, stringify } from './clarity/prettyPrint'; | ||
export { parse } from './clarity/parser'; | ||
export const bool = boolCV; | ||
@@ -5,0 +6,0 @@ export const int = intCV; |
import { ClarityValue } from '.'; | ||
export declare function prettyPrint(cv: ClarityValue, space?: number): string; | ||
export declare function stringify(cv: ClarityValue, space?: number): string; | ||
export declare const prettyPrint: typeof stringify; |
@@ -65,5 +65,6 @@ import { bytesToHex } from '@stacks/common'; | ||
} | ||
export function prettyPrint(cv, space = 0) { | ||
export function stringify(cv, space = 0) { | ||
return prettyPrintWithDepth(cv, space, 0); | ||
} | ||
export const prettyPrint = stringify; | ||
//# sourceMappingURL=prettyPrint.js.map |
@@ -26,4 +26,6 @@ import { AddressHashMode, AddressVersion, RECOVERABLE_ECDSA_SIG_LENGTH_BYTES, StacksMessageType, TransactionVersion, } from './constants'; | ||
case AddressHashMode.SerializeP2SH: | ||
case AddressHashMode.SerializeP2SHNonSequential: | ||
case AddressHashMode.SerializeP2WPKH: | ||
case AddressHashMode.SerializeP2WSH: | ||
case AddressHashMode.SerializeP2WSHNonSequential: | ||
switch (txVersion) { | ||
@@ -30,0 +32,0 @@ case TransactionVersion.Mainnet: |
@@ -77,6 +77,8 @@ export declare enum ChainID { | ||
SerializeP2WPKH = 2, | ||
SerializeP2WSH = 3 | ||
SerializeP2WSH = 3, | ||
SerializeP2SHNonSequential = 5, | ||
SerializeP2WSHNonSequential = 7 | ||
} | ||
export type SingleSigHashMode = AddressHashMode.SerializeP2PKH | AddressHashMode.SerializeP2WPKH; | ||
export type MultiSigHashMode = AddressHashMode.SerializeP2SH | AddressHashMode.SerializeP2WSH; | ||
export type MultiSigHashMode = AddressHashMode.SerializeP2SH | AddressHashMode.SerializeP2WSH | AddressHashMode.SerializeP2SHNonSequential | AddressHashMode.SerializeP2WSHNonSequential; | ||
export declare enum AddressVersion { | ||
@@ -83,0 +85,0 @@ MainnetSingleSig = 22, |
@@ -100,2 +100,4 @@ export var ChainID; | ||
AddressHashMode[AddressHashMode["SerializeP2WSH"] = 3] = "SerializeP2WSH"; | ||
AddressHashMode[AddressHashMode["SerializeP2SHNonSequential"] = 5] = "SerializeP2SHNonSequential"; | ||
AddressHashMode[AddressHashMode["SerializeP2WSHNonSequential"] = 7] = "SerializeP2WSHNonSequential"; | ||
})(AddressHashMode || (AddressHashMode = {})); | ||
@@ -102,0 +104,0 @@ export var AddressVersion; |
@@ -20,2 +20,3 @@ import { BytesReader } from './bytesReader'; | ||
export declare function compressPublicKey(publicKey: string | Uint8Array): StacksPublicKey; | ||
export declare function uncompressPublicKey(publicKey: string | Uint8Array): StacksPublicKey; | ||
export declare function deserializePublicKey(bytesReader: BytesReader): StacksPublicKey; | ||
@@ -22,0 +23,0 @@ export interface StacksPrivateKey { |
@@ -63,2 +63,7 @@ import { hmac } from '@noble/hashes/hmac'; | ||
} | ||
export function uncompressPublicKey(publicKey) { | ||
const hex = typeof publicKey === 'string' ? publicKey : bytesToHex(publicKey); | ||
const compressed = Point.fromHex(hex).toHex(false); | ||
return createStacksPublicKey(compressed); | ||
} | ||
export function deserializePublicKey(bytesReader) { | ||
@@ -65,0 +70,0 @@ const fieldId = bytesReader.readUInt8(); |
import { DeserializationError } from './errors'; | ||
import { PubKeyEncoding, RECOVERABLE_ECDSA_SIG_LENGTH_BYTES, StacksMessageType } from './constants'; | ||
import { compressPublicKey, deserializePublicKey, serializePublicKey, } from './keys'; | ||
import { compressPublicKey, deserializePublicKey, serializePublicKey, uncompressPublicKey, } from './keys'; | ||
import { createMessageSignature } from './common'; | ||
@@ -31,3 +31,3 @@ import { bytesToHex, concatArray, hexToBytes } from '@stacks/common'; | ||
case AuthFieldType.PublicKeyUncompressed: | ||
return createTransactionAuthField(PubKeyEncoding.Uncompressed, deserializePublicKey(bytesReader)); | ||
return createTransactionAuthField(PubKeyEncoding.Uncompressed, uncompressPublicKey(deserializePublicKey(bytesReader).data)); | ||
case AuthFieldType.SignatureCompressed: | ||
@@ -48,18 +48,11 @@ return createTransactionAuthField(PubKeyEncoding.Compressed, deserializeMessageSignature(bytesReader)); | ||
case StacksMessageType.PublicKey: | ||
if (field.pubKeyEncoding == PubKeyEncoding.Compressed) { | ||
bytesArray.push(AuthFieldType.PublicKeyCompressed); | ||
bytesArray.push(serializePublicKey(field.contents)); | ||
} | ||
else { | ||
bytesArray.push(AuthFieldType.PublicKeyUncompressed); | ||
bytesArray.push(serializePublicKey(compressPublicKey(field.contents.data))); | ||
} | ||
bytesArray.push(field.pubKeyEncoding === PubKeyEncoding.Compressed | ||
? AuthFieldType.PublicKeyCompressed | ||
: AuthFieldType.PublicKeyUncompressed); | ||
bytesArray.push(serializePublicKey(compressPublicKey(field.contents.data))); | ||
break; | ||
case StacksMessageType.MessageSignature: | ||
if (field.pubKeyEncoding == PubKeyEncoding.Compressed) { | ||
bytesArray.push(AuthFieldType.SignatureCompressed); | ||
} | ||
else { | ||
bytesArray.push(AuthFieldType.SignatureUncompressed); | ||
} | ||
bytesArray.push(field.pubKeyEncoding === PubKeyEncoding.Compressed | ||
? AuthFieldType.SignatureCompressed | ||
: AuthFieldType.SignatureUncompressed); | ||
bytesArray.push(serializeMessageSignature(field.contents)); | ||
@@ -66,0 +59,0 @@ break; |
@@ -1,4 +0,4 @@ | ||
import { isSingleSig, nextVerification } from './authorization'; | ||
import { isSequentialMultiSig, isSingleSig, nextVerification, } from './authorization'; | ||
import { cloneDeep } from './utils'; | ||
import { AuthType, PubKeyEncoding, StacksMessageType } from './constants'; | ||
import { AddressHashMode, AuthType, PubKeyEncoding, StacksMessageType } from './constants'; | ||
import { SigningError } from './errors'; | ||
@@ -50,4 +50,5 @@ export class TransactionSigner { | ||
} | ||
if (!isSingleSig(this.transaction.auth.spendingCondition)) { | ||
const spendingCondition = this.transaction.auth.spendingCondition; | ||
const spendingCondition = this.transaction.auth.spendingCondition; | ||
if (spendingCondition.hashMode === AddressHashMode.SerializeP2SH || | ||
spendingCondition.hashMode === AddressHashMode.SerializeP2WSH) { | ||
if (this.checkOversign && | ||
@@ -59,3 +60,6 @@ spendingCondition.fields.filter(field => field.contents.type === StacksMessageType.MessageSignature).length >= spendingCondition.signaturesRequired) { | ||
const nextSighash = this.transaction.signNextOrigin(this.sigHash, privateKey); | ||
this.sigHash = nextSighash; | ||
if (isSingleSig(this.transaction.auth.spendingCondition) || | ||
isSequentialMultiSig(this.transaction.auth.spendingCondition.hashMode)) { | ||
this.sigHash = nextSighash; | ||
} | ||
} | ||
@@ -62,0 +66,0 @@ appendOrigin(publicKey) { |
@@ -32,1 +32,3 @@ import { IntegerType } from '@stacks/common'; | ||
export declare function deserializeTransaction(tx: string | Uint8Array | BytesReader): StacksTransaction; | ||
export declare function serializeTransaction(transaction: StacksTransaction): Uint8Array; | ||
export declare function transactionToHex(transaction: StacksTransaction): string; |
@@ -1,2 +0,2 @@ | ||
import { bytesToHex, concatArray, hexToBytes, intToBigInt, writeUInt32BE, } from '@stacks/common'; | ||
import { bytesToHex, concatArray, hexToBytes, intToBigInt, PRIVATE_KEY_COMPRESSED_LENGTH, writeUInt32BE, } from '@stacks/common'; | ||
import { deserializeAuthorization, intoInitialSighashAuth, isSingleSig, nextSignature, serializeAuthorization, setFee, setNonce, setSponsor, setSponsorNonce, verifyOrigin, } from './authorization'; | ||
@@ -94,4 +94,5 @@ import { BytesReader } from './bytesReader'; | ||
else { | ||
const compressed = bytesToHex(privateKey.data).endsWith('01'); | ||
condition.fields.push(createTransactionAuthField(compressed ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed, nextSig)); | ||
condition.fields.push(createTransactionAuthField(privateKey.data.byteLength === PRIVATE_KEY_COMPRESSED_LENGTH | ||
? PubKeyEncoding.Compressed | ||
: PubKeyEncoding.Uncompressed, nextSig)); | ||
} | ||
@@ -182,2 +183,8 @@ return nextSigHash; | ||
} | ||
export function serializeTransaction(transaction) { | ||
return transaction.serialize(); | ||
} | ||
export function transactionToHex(transaction) { | ||
return bytesToHex(transaction.serialize()); | ||
} | ||
//# sourceMappingURL=transaction.js.map |
@@ -86,7 +86,7 @@ import { bytesToHex, bytesToUtf8, concatArray, hexToBytes, hexToInt, intToBytes, intToHex, utf8ToBytes, } from '@stacks/common'; | ||
} | ||
if (hashMode === AddressHashMode.SerializeP2WPKH || hashMode === AddressHashMode.SerializeP2WSH) { | ||
for (let i = 0; i < publicKeys.length; i++) { | ||
if (!isCompressed(publicKeys[i])) { | ||
throw Error('Public keys must be compressed for segwit'); | ||
} | ||
if (hashMode === AddressHashMode.SerializeP2WPKH || | ||
hashMode === AddressHashMode.SerializeP2WSH || | ||
hashMode === AddressHashMode.SerializeP2WSHNonSequential) { | ||
if (!publicKeys.every(isCompressed)) { | ||
throw Error('Public keys must be compressed for segwit'); | ||
} | ||
@@ -100,4 +100,6 @@ } | ||
case AddressHashMode.SerializeP2SH: | ||
case AddressHashMode.SerializeP2SHNonSequential: | ||
return addressFromVersionHash(version, hashP2SH(numSigs, publicKeys.map(serializePublicKey))); | ||
case AddressHashMode.SerializeP2WSH: | ||
case AddressHashMode.SerializeP2WSHNonSequential: | ||
return addressFromVersionHash(version, hashP2WSH(numSigs, publicKeys.map(serializePublicKey))); | ||
@@ -104,0 +106,0 @@ } |
@@ -20,2 +20,3 @@ import { BytesReader } from './bytesReader'; | ||
export declare function compressPublicKey(publicKey: string | Uint8Array): StacksPublicKey; | ||
export declare function uncompressPublicKey(publicKey: string | Uint8Array): StacksPublicKey; | ||
export declare function deserializePublicKey(bytesReader: BytesReader): StacksPublicKey; | ||
@@ -22,0 +23,0 @@ export interface StacksPrivateKey { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.publicKeyToAddress = exports.privateKeyToString = exports.getPublicKey = exports.signMessageHashRsv = exports.signWithKey = exports.makeRandomPrivKey = exports.createStacksPrivateKey = exports.deserializePublicKey = exports.compressPublicKey = exports.pubKeyfromPrivKey = exports.serializePublicKey = exports.publicKeyToString = exports.isCompressed = exports.publicKeyFromBytes = exports.publicKeyFromSignatureRsv = exports.publicKeyFromSignatureVrs = exports.createStacksPublicKey = exports.getAddressFromPublicKey = exports.getAddressFromPrivateKey = void 0; | ||
exports.publicKeyToAddress = exports.privateKeyToString = exports.getPublicKey = exports.signMessageHashRsv = exports.signWithKey = exports.makeRandomPrivKey = exports.createStacksPrivateKey = exports.deserializePublicKey = exports.uncompressPublicKey = exports.compressPublicKey = exports.pubKeyfromPrivKey = exports.serializePublicKey = exports.publicKeyToString = exports.isCompressed = exports.publicKeyFromBytes = exports.publicKeyFromSignatureRsv = exports.publicKeyFromSignatureVrs = exports.createStacksPublicKey = exports.getAddressFromPublicKey = exports.getAddressFromPrivateKey = void 0; | ||
const hmac_1 = require("@noble/hashes/hmac"); | ||
@@ -77,2 +77,8 @@ const sha256_1 = require("@noble/hashes/sha256"); | ||
exports.compressPublicKey = compressPublicKey; | ||
function uncompressPublicKey(publicKey) { | ||
const hex = typeof publicKey === 'string' ? publicKey : (0, common_1.bytesToHex)(publicKey); | ||
const compressed = secp256k1_1.Point.fromHex(hex).toHex(false); | ||
return createStacksPublicKey(compressed); | ||
} | ||
exports.uncompressPublicKey = uncompressPublicKey; | ||
function deserializePublicKey(bytesReader) { | ||
@@ -79,0 +85,0 @@ const fieldId = bytesReader.readUInt8(); |
@@ -36,3 +36,3 @@ "use strict"; | ||
case AuthFieldType.PublicKeyUncompressed: | ||
return createTransactionAuthField(constants_1.PubKeyEncoding.Uncompressed, (0, keys_1.deserializePublicKey)(bytesReader)); | ||
return createTransactionAuthField(constants_1.PubKeyEncoding.Uncompressed, (0, keys_1.uncompressPublicKey)((0, keys_1.deserializePublicKey)(bytesReader).data)); | ||
case AuthFieldType.SignatureCompressed: | ||
@@ -55,18 +55,11 @@ return createTransactionAuthField(constants_1.PubKeyEncoding.Compressed, deserializeMessageSignature(bytesReader)); | ||
case constants_1.StacksMessageType.PublicKey: | ||
if (field.pubKeyEncoding == constants_1.PubKeyEncoding.Compressed) { | ||
bytesArray.push(AuthFieldType.PublicKeyCompressed); | ||
bytesArray.push((0, keys_1.serializePublicKey)(field.contents)); | ||
} | ||
else { | ||
bytesArray.push(AuthFieldType.PublicKeyUncompressed); | ||
bytesArray.push((0, keys_1.serializePublicKey)((0, keys_1.compressPublicKey)(field.contents.data))); | ||
} | ||
bytesArray.push(field.pubKeyEncoding === constants_1.PubKeyEncoding.Compressed | ||
? AuthFieldType.PublicKeyCompressed | ||
: AuthFieldType.PublicKeyUncompressed); | ||
bytesArray.push((0, keys_1.serializePublicKey)((0, keys_1.compressPublicKey)(field.contents.data))); | ||
break; | ||
case constants_1.StacksMessageType.MessageSignature: | ||
if (field.pubKeyEncoding == constants_1.PubKeyEncoding.Compressed) { | ||
bytesArray.push(AuthFieldType.SignatureCompressed); | ||
} | ||
else { | ||
bytesArray.push(AuthFieldType.SignatureUncompressed); | ||
} | ||
bytesArray.push(field.pubKeyEncoding === constants_1.PubKeyEncoding.Compressed | ||
? AuthFieldType.SignatureCompressed | ||
: AuthFieldType.SignatureUncompressed); | ||
bytesArray.push(serializeMessageSignature(field.contents)); | ||
@@ -73,0 +66,0 @@ break; |
@@ -53,4 +53,5 @@ "use strict"; | ||
} | ||
if (!(0, authorization_1.isSingleSig)(this.transaction.auth.spendingCondition)) { | ||
const spendingCondition = this.transaction.auth.spendingCondition; | ||
const spendingCondition = this.transaction.auth.spendingCondition; | ||
if (spendingCondition.hashMode === constants_1.AddressHashMode.SerializeP2SH || | ||
spendingCondition.hashMode === constants_1.AddressHashMode.SerializeP2WSH) { | ||
if (this.checkOversign && | ||
@@ -62,3 +63,6 @@ spendingCondition.fields.filter(field => field.contents.type === constants_1.StacksMessageType.MessageSignature).length >= spendingCondition.signaturesRequired) { | ||
const nextSighash = this.transaction.signNextOrigin(this.sigHash, privateKey); | ||
this.sigHash = nextSighash; | ||
if ((0, authorization_1.isSingleSig)(this.transaction.auth.spendingCondition) || | ||
(0, authorization_1.isSequentialMultiSig)(this.transaction.auth.spendingCondition.hashMode)) { | ||
this.sigHash = nextSighash; | ||
} | ||
} | ||
@@ -65,0 +69,0 @@ appendOrigin(publicKey) { |
@@ -32,1 +32,3 @@ import { IntegerType } from '@stacks/common'; | ||
export declare function deserializeTransaction(tx: string | Uint8Array | BytesReader): StacksTransaction; | ||
export declare function serializeTransaction(transaction: StacksTransaction): Uint8Array; | ||
export declare function transactionToHex(transaction: StacksTransaction): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.deserializeTransaction = exports.StacksTransaction = void 0; | ||
exports.transactionToHex = exports.serializeTransaction = exports.deserializeTransaction = exports.StacksTransaction = void 0; | ||
const common_1 = require("@stacks/common"); | ||
@@ -97,4 +97,5 @@ const authorization_1 = require("./authorization"); | ||
else { | ||
const compressed = (0, common_1.bytesToHex)(privateKey.data).endsWith('01'); | ||
condition.fields.push((0, signature_1.createTransactionAuthField)(compressed ? constants_1.PubKeyEncoding.Compressed : constants_1.PubKeyEncoding.Uncompressed, nextSig)); | ||
condition.fields.push((0, signature_1.createTransactionAuthField)(privateKey.data.byteLength === common_1.PRIVATE_KEY_COMPRESSED_LENGTH | ||
? constants_1.PubKeyEncoding.Compressed | ||
: constants_1.PubKeyEncoding.Uncompressed, nextSig)); | ||
} | ||
@@ -187,2 +188,10 @@ return nextSigHash; | ||
exports.deserializeTransaction = deserializeTransaction; | ||
function serializeTransaction(transaction) { | ||
return transaction.serialize(); | ||
} | ||
exports.serializeTransaction = serializeTransaction; | ||
function transactionToHex(transaction) { | ||
return (0, common_1.bytesToHex)(transaction.serialize()); | ||
} | ||
exports.transactionToHex = transactionToHex; | ||
//# sourceMappingURL=transaction.js.map |
@@ -93,7 +93,7 @@ "use strict"; | ||
} | ||
if (hashMode === constants_1.AddressHashMode.SerializeP2WPKH || hashMode === constants_1.AddressHashMode.SerializeP2WSH) { | ||
for (let i = 0; i < publicKeys.length; i++) { | ||
if (!(0, keys_1.isCompressed)(publicKeys[i])) { | ||
throw Error('Public keys must be compressed for segwit'); | ||
} | ||
if (hashMode === constants_1.AddressHashMode.SerializeP2WPKH || | ||
hashMode === constants_1.AddressHashMode.SerializeP2WSH || | ||
hashMode === constants_1.AddressHashMode.SerializeP2WSHNonSequential) { | ||
if (!publicKeys.every(keys_1.isCompressed)) { | ||
throw Error('Public keys must be compressed for segwit'); | ||
} | ||
@@ -107,4 +107,6 @@ } | ||
case constants_1.AddressHashMode.SerializeP2SH: | ||
case constants_1.AddressHashMode.SerializeP2SHNonSequential: | ||
return (0, common_2.addressFromVersionHash)(version, (0, utils_1.hashP2SH)(numSigs, publicKeys.map(keys_1.serializePublicKey))); | ||
case constants_1.AddressHashMode.SerializeP2WSH: | ||
case constants_1.AddressHashMode.SerializeP2WSHNonSequential: | ||
return (0, common_2.addressFromVersionHash)(version, (0, utils_1.hashP2WSH)(numSigs, publicKeys.map(keys_1.serializePublicKey))); | ||
@@ -111,0 +113,0 @@ } |
{ | ||
"name": "@stacks/transactions", | ||
"version": "6.15.1-pr.516f0cae.3+516f0cae", | ||
"version": "6.15.1-pr.56e21ff8.15+56e21ff8", | ||
"description": "Javascript library for constructing transactions on the Stacks blockchain.", | ||
@@ -30,4 +30,4 @@ "license": "MIT", | ||
"@noble/secp256k1": "1.7.1", | ||
"@stacks/common": "^6.15.1-pr.516f0cae.3+516f0cae", | ||
"@stacks/network": "^6.15.1-pr.516f0cae.3+516f0cae", | ||
"@stacks/common": "^6.15.1-pr.56e21ff8.15+56e21ff8", | ||
"@stacks/network": "^6.15.1-pr.56e21ff8.15+56e21ff8", | ||
"c32check": "^2.0.0", | ||
@@ -37,3 +37,3 @@ "lodash.clonedeep": "^4.5.0" | ||
"devDependencies": { | ||
"@stacks/encryption": "^6.15.1-pr.516f0cae.3+516f0cae", | ||
"@stacks/encryption": "^6.15.1-pr.56e21ff8.15+56e21ff8", | ||
"@types/common-tags": "^1.8.0", | ||
@@ -67,3 +67,3 @@ "@types/elliptic": "^6.4.12", | ||
}, | ||
"gitHead": "516f0cae6aa2993f051154f751ebb3343f49923f" | ||
"gitHead": "56e21ff8c23e1132d0529be2aaf8b5d21d832343" | ||
} |
@@ -143,2 +143,3 @@ import { | ||
/** @internal */ | ||
export function isSingleSig( | ||
@@ -150,2 +151,15 @@ condition: SpendingConditionOpts | ||
/** @internal */ | ||
export function isSequentialMultiSig(hashMode: AddressHashMode): boolean { | ||
return hashMode === AddressHashMode.SerializeP2SH || hashMode === AddressHashMode.SerializeP2WSH; | ||
} | ||
/** @internal */ | ||
export function isNonSequentialMultiSig(hashMode: AddressHashMode): boolean { | ||
return ( | ||
hashMode === AddressHashMode.SerializeP2SHNonSequential || | ||
hashMode === AddressHashMode.SerializeP2WSHNonSequential | ||
); | ||
} | ||
function clearCondition(condition: SpendingConditionOpts): SpendingCondition { | ||
@@ -264,4 +278,9 @@ const cloned = cloneDeep(condition); | ||
if (haveUncompressed && hashMode === AddressHashMode.SerializeP2SH) | ||
if ( | ||
haveUncompressed && | ||
(hashMode === AddressHashMode.SerializeP2WSH || | ||
hashMode === AddressHashMode.SerializeP2WSHNonSequential) | ||
) { | ||
throw new VerificationError('Uncompressed keys are not allowed in this hash mode'); | ||
} | ||
@@ -454,2 +473,3 @@ return { | ||
const publicKeys: StacksPublicKey[] = []; | ||
let curSigHash = initialSigHash; | ||
@@ -460,8 +480,6 @@ let haveUncompressed = false; | ||
for (const field of condition.fields) { | ||
let foundPubKey: StacksPublicKey; | ||
switch (field.contents.type) { | ||
case StacksMessageType.PublicKey: | ||
if (!isCompressed(field.contents)) haveUncompressed = true; | ||
foundPubKey = field.contents; | ||
publicKeys.push(field.contents); | ||
break; | ||
@@ -478,17 +496,26 @@ case StacksMessageType.MessageSignature: | ||
); | ||
curSigHash = nextSigHash; | ||
foundPubKey = pubKey; | ||
if (isSequentialMultiSig(condition.hashMode)) { | ||
curSigHash = nextSigHash; | ||
} | ||
publicKeys.push(pubKey); | ||
numSigs += 1; | ||
if (numSigs === 65536) throw new VerificationError('Too many signatures'); | ||
break; | ||
} | ||
publicKeys.push(foundPubKey); | ||
} | ||
if (numSigs !== condition.signaturesRequired) | ||
if ( | ||
(isSequentialMultiSig(condition.hashMode) && numSigs !== condition.signaturesRequired) || | ||
(isNonSequentialMultiSig(condition.hashMode) && numSigs < condition.signaturesRequired) | ||
) | ||
throw new VerificationError('Incorrect number of signatures'); | ||
if (haveUncompressed && condition.hashMode === AddressHashMode.SerializeP2SH) | ||
if ( | ||
haveUncompressed && | ||
(condition.hashMode === AddressHashMode.SerializeP2WSH || | ||
condition.hashMode === AddressHashMode.SerializeP2WSHNonSequential) | ||
) | ||
throw new VerificationError('Uncompressed keys are not allowed in this hash mode'); | ||
@@ -563,3 +590,3 @@ | ||
case AuthType.Sponsored: | ||
return verify(auth.spendingCondition, initialSigHash, AuthType.Standard); | ||
return verify(auth.spendingCondition, initialSigHash, AuthType.Standard); // todo: should this be .Sponsored? | ||
default: | ||
@@ -566,0 +593,0 @@ throw new SigningError('Invalid origin auth type'); |
@@ -19,2 +19,3 @@ import { bytesToHex, hexToBytes, IntegerType, intToBigInt } from '@stacks/common'; | ||
MultiSigSpendingCondition, | ||
isSingleSig, | ||
} from './authorization'; | ||
@@ -37,2 +38,3 @@ import { ClarityValue, deserializeCV, NoneCV, PrincipalCV, serializeCV } from './clarity'; | ||
AnchorModeName, | ||
MultiSigHashMode, | ||
} from './constants'; | ||
@@ -43,2 +45,3 @@ import { ClarityAbi, validateContractCall } from './contract-abi'; | ||
createStacksPrivateKey, | ||
createStacksPublicKey, | ||
getPublicKey, | ||
@@ -64,2 +67,3 @@ pubKeyfromPrivKey, | ||
AssetInfo, | ||
createAddress, | ||
createContractPrincipal, | ||
@@ -74,15 +78,21 @@ createStandardPrincipal, | ||
import { StacksTransaction } from './transaction'; | ||
import { createLPList } from './types'; | ||
import { addressFromPublicKeys, createLPList } from './types'; | ||
import { cvToHex, omit, parseReadOnlyResponse, validateTxId } from './utils'; | ||
/** @internal */ | ||
async function _getNonceApi(address: string, network: StacksNetwork): Promise<bigint> { | ||
const url = `${network.coreApiUrl}/extended/v1/address/${address}/nonces`; | ||
const response = await network.fetchFn(url); | ||
const result = await response.json(); | ||
return BigInt(result.possible_next_nonce); | ||
} | ||
/** | ||
* Lookup the nonce for an address from a core node | ||
* | ||
* @param {string} address - the c32check address to look up | ||
* @param {StacksNetworkName | StacksNetwork} network - the Stacks network to look up address on | ||
* | ||
* @return a promise that resolves to an integer | ||
* Lookup the nonce for an address from an API or core node | ||
* @return a promise that resolves to a bigint | ||
*/ | ||
export async function getNonce( | ||
/** The Stacks (c32check) address to look up */ | ||
address: string, | ||
/** The Stacks network to look up the address on */ | ||
network?: StacksNetworkName | StacksNetwork | ||
@@ -93,2 +103,8 @@ ): Promise<bigint> { | ||
// Try API first | ||
try { | ||
return await _getNonceApi(address, derivedNetwork); | ||
} catch (e) {} | ||
// Try node if API endpoint isn't available | ||
const response = await derivedNetwork.fetchFn(url); | ||
@@ -590,2 +606,3 @@ if (!response.ok) { | ||
/** @deprecated Not used internally */ | ||
export interface MultiSigOptions { | ||
@@ -597,2 +614,22 @@ numSignatures: number; | ||
export interface UnsignedMultiSigOptions { | ||
/** The minimum required signatures N (in a N of M multi-sig) */ | ||
numSignatures: number; | ||
/** The M public-keys (in a N of M multi-sig), which together form the address of the multi-sig account */ | ||
publicKeys: string[]; | ||
/** | ||
* The `address` of the multi-sig account. | ||
* - If NOT provided, the public-key order is taken AS IS. | ||
* - If provided, the address will be checked against the order of the public-keys (either AS IS or SORTED). | ||
* The default is to SORT the public-keys (only if the `address` is provided). | ||
*/ | ||
address?: string; | ||
/** @experimental Use newer non-sequential multi-sig hashmode for transaction. Future releases may make this the default. */ | ||
useNonSequentialMultiSig?: boolean; | ||
} | ||
export type SignedMultiSigOptions = UnsignedMultiSigOptions & { | ||
signerKeys: string[]; | ||
}; | ||
/** | ||
@@ -629,12 +666,5 @@ * STX token transfer transaction options | ||
export interface UnsignedMultiSigTokenTransferOptions extends TokenTransferOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export type UnsignedMultiSigTokenTransferOptions = TokenTransferOptions & UnsignedMultiSigOptions; | ||
export interface SignedMultiSigTokenTransferOptions extends TokenTransferOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type SignedMultiSigTokenTransferOptions = TokenTransferOptions & SignedMultiSigOptions; | ||
@@ -678,6 +708,19 @@ /** | ||
// multi-sig | ||
const hashMode = options.useNonSequentialMultiSig | ||
? AddressHashMode.SerializeP2SHNonSequential | ||
: AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder( | ||
options.publicKeys, | ||
options.numSignatures, | ||
hashMode, | ||
createAddress(options.address).hash160 | ||
) | ||
: options.publicKeys; | ||
spendingCondition = createMultiSigSpendingCondition( | ||
AddressHashMode.SerializeP2SH, | ||
hashMode, | ||
options.numSignatures, | ||
options.publicKeys, | ||
publicKeys, | ||
options.nonce, | ||
@@ -737,3 +780,3 @@ options.fee | ||
if ('senderKey' in txOptions) { | ||
// txOptions is SignedTokenTransferOptions | ||
// single-sig | ||
const publicKey = publicKeyToString(getPublicKey(createStacksPrivateKey(txOptions.senderKey))); | ||
@@ -749,18 +792,13 @@ const options = omit(txOptions, 'senderKey'); | ||
} else { | ||
// txOptions is SignedMultiSigTokenTransferOptions | ||
// multi-sig | ||
const options = omit(txOptions, 'signerKeys'); | ||
const transaction = await makeUnsignedSTXTokenTransfer(options); | ||
const signer = new TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = pubKeyfromPrivKey(key); | ||
pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); | ||
signer.signOrigin(createStacksPrivateKey(key)); | ||
} | ||
mutatingSignAppendMultiSig( | ||
transaction, | ||
txOptions.publicKeys.slice(), | ||
txOptions.signerKeys, | ||
txOptions.address | ||
); | ||
for (const key of pubKeys) { | ||
signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); | ||
} | ||
return transaction; | ||
@@ -808,12 +846,6 @@ } | ||
export interface UnsignedMultiSigContractDeployOptions extends BaseContractDeployOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export type UnsignedMultiSigContractDeployOptions = BaseContractDeployOptions & | ||
UnsignedMultiSigOptions; | ||
export interface SignedMultiSigContractDeployOptions extends BaseContractDeployOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type SignedMultiSigContractDeployOptions = BaseContractDeployOptions & SignedMultiSigOptions; | ||
@@ -885,3 +917,3 @@ /** | ||
if ('senderKey' in txOptions) { | ||
// txOptions is SignedContractDeployOptions | ||
// single-sig | ||
const publicKey = publicKeyToString(getPublicKey(createStacksPrivateKey(txOptions.senderKey))); | ||
@@ -897,18 +929,13 @@ const options = omit(txOptions, 'senderKey'); | ||
} else { | ||
// txOptions is SignedMultiSigContractDeployOptions | ||
// multi-sig | ||
const options = omit(txOptions, 'signerKeys'); | ||
const transaction = await makeUnsignedContractDeploy(options); | ||
const signer = new TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = pubKeyfromPrivKey(key); | ||
pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); | ||
signer.signOrigin(createStacksPrivateKey(key)); | ||
} | ||
mutatingSignAppendMultiSig( | ||
transaction, | ||
txOptions.publicKeys.slice(), | ||
txOptions.signerKeys, | ||
txOptions.address | ||
); | ||
for (const key of pubKeys) { | ||
signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); | ||
} | ||
return transaction; | ||
@@ -952,6 +979,19 @@ } | ||
// multi-sig | ||
const hashMode = options.useNonSequentialMultiSig | ||
? AddressHashMode.SerializeP2SHNonSequential | ||
: AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder( | ||
options.publicKeys, | ||
options.numSignatures, | ||
hashMode, | ||
createAddress(options.address).hash160 | ||
) | ||
: options.publicKeys; | ||
spendingCondition = createMultiSigSpendingCondition( | ||
AddressHashMode.SerializeP2SH, | ||
hashMode, | ||
options.numSignatures, | ||
options.publicKeys, | ||
publicKeys, | ||
options.nonce, | ||
@@ -1045,12 +1085,5 @@ options.fee | ||
export interface UnsignedMultiSigContractCallOptions extends ContractCallOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
} | ||
export type UnsignedMultiSigContractCallOptions = ContractCallOptions & UnsignedMultiSigOptions; | ||
export interface SignedMultiSigContractCallOptions extends ContractCallOptions { | ||
numSignatures: number; | ||
publicKeys: string[]; | ||
signerKeys: string[]; | ||
} | ||
export type SignedMultiSigContractCallOptions = ContractCallOptions & SignedMultiSigOptions; | ||
@@ -1161,6 +1194,19 @@ /** | ||
// multi-sig | ||
const hashMode = options.useNonSequentialMultiSig | ||
? AddressHashMode.SerializeP2SHNonSequential | ||
: AddressHashMode.SerializeP2SH; | ||
const publicKeys = options.address | ||
? ensureValidMultiSigPublicKeyOrder( | ||
options.publicKeys, | ||
options.numSignatures, | ||
hashMode, | ||
createAddress(options.address).hash160 | ||
) | ||
: options.publicKeys; | ||
spendingCondition = createMultiSigSpendingCondition( | ||
AddressHashMode.SerializeP2SH, | ||
hashMode, | ||
options.numSignatures, | ||
options.publicKeys, | ||
publicKeys, | ||
options.nonce, | ||
@@ -1228,2 +1274,3 @@ options.fee | ||
if ('senderKey' in txOptions) { | ||
// single-sig | ||
const publicKey = publicKeyToString(getPublicKey(createStacksPrivateKey(txOptions.senderKey))); | ||
@@ -1239,17 +1286,13 @@ const options = omit(txOptions, 'senderKey'); | ||
} else { | ||
// multi-sig | ||
const options = omit(txOptions, 'signerKeys'); | ||
const transaction = await makeUnsignedContractCall(options); | ||
const signer = new TransactionSigner(transaction); | ||
let pubKeys = txOptions.publicKeys; | ||
for (const key of txOptions.signerKeys) { | ||
const pubKey = pubKeyfromPrivKey(key); | ||
pubKeys = pubKeys.filter(pk => pk !== bytesToHex(pubKey.data)); | ||
signer.signOrigin(createStacksPrivateKey(key)); | ||
} | ||
mutatingSignAppendMultiSig( | ||
transaction, | ||
txOptions.publicKeys.slice(), | ||
txOptions.signerKeys, | ||
txOptions.address | ||
); | ||
for (const key of pubKeys) { | ||
signer.appendOrigin(publicKeyFromBytes(hexToBytes(key))); | ||
} | ||
return transaction; | ||
@@ -1703,1 +1746,68 @@ } | ||
} | ||
/** @internal multi-sig signing re-use */ | ||
function mutatingSignAppendMultiSig( | ||
/** **Warning:** method mutates `transaction` */ | ||
transaction: StacksTransaction, | ||
publicKeys: string[], | ||
signerKeys: string[], | ||
address?: string | ||
) { | ||
if (isSingleSig(transaction.auth.spendingCondition)) { | ||
throw new Error('Transaction is not a multi-sig transaction'); | ||
} | ||
const signer = new TransactionSigner(transaction); | ||
const pubs = ensureValidMultiSigPublicKeyOrder( | ||
publicKeys, | ||
transaction.auth.spendingCondition.signaturesRequired, | ||
transaction.auth.spendingCondition.hashMode, | ||
address ? createAddress(address).hash160 : undefined | ||
); | ||
// sign in order of public keys | ||
for (const publicKey of pubs) { | ||
const signerKey = signerKeys.find(key => bytesToHex(pubKeyfromPrivKey(key).data) === publicKey); | ||
if (signerKey) { | ||
// either sign and append message signature (which allows for recovering the public key) | ||
signer.signOrigin(createStacksPrivateKey(signerKey)); | ||
} else { | ||
// or append the public key (which did not sign here) | ||
signer.appendOrigin(publicKeyFromBytes(hexToBytes(publicKey))); | ||
} | ||
} | ||
} | ||
/** @internal Get the matching public-keys array for a multi-sig address */ | ||
function ensureValidMultiSigPublicKeyOrder( | ||
publicKeys: string[], | ||
numSigs: number, | ||
hashMode: MultiSigHashMode, | ||
hash?: string | ||
): string[] { | ||
if (!hash) return publicKeys; | ||
// unsorted | ||
const hashUnsorted = addressFromPublicKeys( | ||
0 as any, // only used for hash, so version doesn't matter | ||
hashMode, | ||
numSigs, | ||
publicKeys.map(createStacksPublicKey) | ||
).hash160; | ||
if (hashUnsorted === hash) return publicKeys; | ||
// sorted | ||
const publicKeysSorted = publicKeys.slice().sort(); | ||
const hashSorted = addressFromPublicKeys( | ||
0 as any, // only used for hash, so version doesn't matter | ||
hashMode, | ||
numSigs, | ||
publicKeysSorted.map(createStacksPublicKey) | ||
).hash160; | ||
if (hashSorted === hash) return publicKeysSorted; | ||
throw new Error('Failed to find matching multi-sig address given public-keys.'); | ||
} |
@@ -21,3 +21,4 @@ import { asciiToBytes, hexToBytes, utf8ToBytes } from '@stacks/common'; | ||
export { prettyPrint } from './clarity/prettyPrint'; | ||
export { prettyPrint, stringify } from './clarity/prettyPrint'; | ||
export { parse } from './clarity/parser'; | ||
@@ -24,0 +25,0 @@ // todo: https://github.com/hirosystems/clarinet/issues/786 |
@@ -113,4 +113,4 @@ /* | ||
/** | ||
* @description format clarity values in clarity style strings | ||
* with the ability to prettify the result with line break end space indentation | ||
* Format clarity values in clarity style strings with the ability to prettify | ||
* the result with line break end space indentation. | ||
* @param cv The Clarity Value to format | ||
@@ -130,4 +130,7 @@ * @param space The indentation size of the output string. There's no indentation and no line breaks if space = 0 | ||
*/ | ||
export function prettyPrint(cv: ClarityValue, space = 0): string { | ||
export function stringify(cv: ClarityValue, space = 0): string { | ||
return prettyPrintWithDepth(cv, space, 0); | ||
} | ||
/** @deprecated alias for {@link Cl.stringify} */ | ||
export const prettyPrint = stringify; |
@@ -43,2 +43,3 @@ import { | ||
): AddressVersion { | ||
// todo: `next` refacto with network param | ||
switch (hashMode) { | ||
@@ -57,4 +58,6 @@ case AddressHashMode.SerializeP2PKH: | ||
case AddressHashMode.SerializeP2SH: | ||
case AddressHashMode.SerializeP2SHNonSequential: | ||
case AddressHashMode.SerializeP2WPKH: | ||
case AddressHashMode.SerializeP2WSH: | ||
case AddressHashMode.SerializeP2WSHNonSequential: | ||
switch (txVersion) { | ||
@@ -61,0 +64,0 @@ case TransactionVersion.Mainnet: |
@@ -166,12 +166,23 @@ /** | ||
SerializeP2PKH = 0x00, | ||
/** `MultiSigHashMode` — hash160(multisig-redeem-script), same as bitcoin's multisig p2sh */ | ||
/** Legacy `MultiSigHashMode` — hash160(multisig-redeem-script), same as bitcoin's multisig p2sh */ | ||
SerializeP2SH = 0x01, | ||
/** `SingleSigHashMode` — hash160(segwit-program-00(p2pkh)), same as bitcoin's p2sh-p2wpkh */ | ||
SerializeP2WPKH = 0x02, | ||
/** `MultiSigHashMode` — hash160(segwit-program-00(public-keys)), same as bitcoin's p2sh-p2wsh */ | ||
/** Legacy `MultiSigHashMode` — hash160(segwit-program-00(public-keys)), same as bitcoin's p2sh-p2wsh */ | ||
SerializeP2WSH = 0x03, | ||
/** Non-Sequential `MultiSigHashMode` — hash160(multisig-redeem-script), same as bitcoin's multisig p2sh */ | ||
SerializeP2SHNonSequential = 0x05, | ||
/** Non-Sequential `MultiSigHashMode` — hash160(segwit-program-00(public-keys)), same as bitcoin's p2sh-p2wsh */ | ||
SerializeP2WSHNonSequential = 0x07, | ||
// todo: `next` rename to remove the `Serialize` prefix? | ||
// todo: `next` rename to remove `NonSequential` and add `Legacy` to sequential mutlisig | ||
} | ||
export type SingleSigHashMode = AddressHashMode.SerializeP2PKH | AddressHashMode.SerializeP2WPKH; | ||
export type MultiSigHashMode = AddressHashMode.SerializeP2SH | AddressHashMode.SerializeP2WSH; | ||
export type MultiSigHashMode = | ||
| AddressHashMode.SerializeP2SH | ||
| AddressHashMode.SerializeP2WSH | ||
| AddressHashMode.SerializeP2SHNonSequential | ||
| AddressHashMode.SerializeP2WSHNonSequential; | ||
@@ -178,0 +189,0 @@ /** |
@@ -143,2 +143,8 @@ import { hmac } from '@noble/hashes/hmac'; | ||
export function uncompressPublicKey(publicKey: string | Uint8Array): StacksPublicKey { | ||
const hex = typeof publicKey === 'string' ? publicKey : bytesToHex(publicKey); | ||
const compressed = Point.fromHex(hex).toHex(false); | ||
return createStacksPublicKey(compressed); | ||
} | ||
export function deserializePublicKey(bytesReader: BytesReader): StacksPublicKey { | ||
@@ -165,2 +171,3 @@ const fieldId = bytesReader.readUInt8(); | ||
export function makeRandomPrivKey(): StacksPrivateKey { | ||
// todo: `next` default to compressed private key | ||
return createStacksPrivateKey(utils.randomPrivateKey()); | ||
@@ -167,0 +174,0 @@ } |
@@ -9,2 +9,3 @@ import { BytesReader } from './bytesReader'; | ||
StacksPublicKey, | ||
uncompressPublicKey, | ||
} from './keys'; | ||
@@ -38,2 +39,4 @@ | ||
// todo: `next` refactor to match wire format more precisely eg https://github.com/jbencin/sips/blob/sip-02x-non-sequential-multisig-transactions/sips/sip-02x/sip-02x-non-sequential-multisig-transactions.md | ||
// "A spending authorization field is encoded as follows:" ... | ||
export interface TransactionAuthField { | ||
@@ -70,3 +73,3 @@ type: StacksMessageType.TransactionAuthField; | ||
PubKeyEncoding.Uncompressed, | ||
deserializePublicKey(bytesReader) | ||
uncompressPublicKey(deserializePublicKey(bytesReader).data) | ||
); | ||
@@ -97,16 +100,15 @@ case AuthFieldType.SignatureCompressed: | ||
case StacksMessageType.PublicKey: | ||
if (field.pubKeyEncoding == PubKeyEncoding.Compressed) { | ||
bytesArray.push(AuthFieldType.PublicKeyCompressed); | ||
bytesArray.push(serializePublicKey(field.contents)); | ||
} else { | ||
bytesArray.push(AuthFieldType.PublicKeyUncompressed); | ||
bytesArray.push(serializePublicKey(compressPublicKey(field.contents.data))); | ||
} | ||
bytesArray.push( | ||
field.pubKeyEncoding === PubKeyEncoding.Compressed | ||
? AuthFieldType.PublicKeyCompressed | ||
: AuthFieldType.PublicKeyUncompressed | ||
); | ||
bytesArray.push(serializePublicKey(compressPublicKey(field.contents.data))); | ||
break; | ||
case StacksMessageType.MessageSignature: | ||
if (field.pubKeyEncoding == PubKeyEncoding.Compressed) { | ||
bytesArray.push(AuthFieldType.SignatureCompressed); | ||
} else { | ||
bytesArray.push(AuthFieldType.SignatureUncompressed); | ||
} | ||
bytesArray.push( | ||
field.pubKeyEncoding === PubKeyEncoding.Compressed | ||
? AuthFieldType.SignatureCompressed | ||
: AuthFieldType.SignatureUncompressed | ||
); | ||
bytesArray.push(serializeMessageSignature(field.contents)); | ||
@@ -113,0 +115,0 @@ break; |
import { StacksTransaction } from './transaction'; | ||
import { StacksPrivateKey, StacksPublicKey } from './keys'; | ||
import { isSingleSig, nextVerification, SpendingConditionOpts } from './authorization'; | ||
import { | ||
isSequentialMultiSig, | ||
isSingleSig, | ||
nextVerification, | ||
SpendingConditionOpts, | ||
} from './authorization'; | ||
import { cloneDeep } from './utils'; | ||
import { AuthType, PubKeyEncoding, StacksMessageType } from './constants'; | ||
import { AddressHashMode, AuthType, PubKeyEncoding, StacksMessageType } from './constants'; | ||
import { SigningError } from './errors'; | ||
// todo: get rid of signer and combine with transaction class? could reduce code and complexity by calculating sighash newly each sign and append. | ||
export class TransactionSigner { | ||
@@ -83,4 +88,8 @@ transaction: StacksTransaction; | ||
if (!isSingleSig(this.transaction.auth.spendingCondition)) { | ||
const spendingCondition = this.transaction.auth.spendingCondition; | ||
const spendingCondition = this.transaction.auth.spendingCondition; | ||
if ( | ||
spendingCondition.hashMode === AddressHashMode.SerializeP2SH || | ||
spendingCondition.hashMode === AddressHashMode.SerializeP2WSH | ||
) { | ||
// only check oversign on legacy multisig modes | ||
if ( | ||
@@ -97,3 +106,9 @@ this.checkOversign && | ||
const nextSighash = this.transaction.signNextOrigin(this.sigHash, privateKey); | ||
this.sigHash = nextSighash; | ||
if ( | ||
isSingleSig(this.transaction.auth.spendingCondition) || | ||
isSequentialMultiSig(this.transaction.auth.spendingCondition.hashMode) | ||
) { | ||
this.sigHash = nextSighash; | ||
} | ||
} | ||
@@ -100,0 +115,0 @@ |
@@ -7,2 +7,3 @@ import { | ||
intToBigInt, | ||
PRIVATE_KEY_COMPRESSED_LENGTH, | ||
writeUInt32BE, | ||
@@ -98,2 +99,3 @@ } from '@stacks/common'; | ||
/** @deprecated Does NOT mutate transaction, but rather returns the hash of the transaction with a cleared initial authorization */ | ||
signBegin() { | ||
@@ -105,2 +107,3 @@ const tx = cloneDeep(this); | ||
/** @deprecated Alias of `.signBegin()` */ | ||
verifyBegin() { | ||
@@ -154,3 +157,6 @@ const tx = cloneDeep(this); | ||
// todo: this could be static? | ||
/** **Warning**: method mutates the `condition` param */ | ||
signAndAppend( | ||
/** `condition` is mutated by this method */ | ||
condition: SpendingConditionOpts, | ||
@@ -171,6 +177,7 @@ curSigHash: string, | ||
} else { | ||
const compressed = bytesToHex(privateKey.data).endsWith('01'); | ||
condition.fields.push( | ||
createTransactionAuthField( | ||
compressed ? PubKeyEncoding.Compressed : PubKeyEncoding.Uncompressed, | ||
privateKey.data.byteLength === PRIVATE_KEY_COMPRESSED_LENGTH | ||
? PubKeyEncoding.Compressed | ||
: PubKeyEncoding.Uncompressed, | ||
nextSig | ||
@@ -265,3 +272,3 @@ ) | ||
export function deserializeTransaction(tx: string | Uint8Array | BytesReader) { | ||
let bytesReader: BytesReader; | ||
let bytesReader: BytesReader; // todo: add readerFrom method | ||
if (typeof tx === 'string') { | ||
@@ -302,1 +309,34 @@ if (tx.slice(0, 2).toLowerCase() === '0x') { | ||
} | ||
/** | ||
* Alias for `transaction.serialize()` | ||
* | ||
* Serializes a transaction to bytes. | ||
* | ||
* @example | ||
* ```ts | ||
* import { makeSTXTokenTransfer, serializeTransaction } from '@stacks/transactions'; | ||
* | ||
* const transaction = makeSTXTokenTransfer({ ... }); | ||
* const bytes = serializeTransaction(transaction); | ||
* ``` | ||
*/ | ||
export function serializeTransaction(transaction: StacksTransaction): Uint8Array { | ||
// todo: refactor to hex instead of bytes for `next` release | ||
return transaction.serialize(); | ||
} | ||
/** | ||
* Serializes a transaction to a hex string. | ||
* | ||
* @example | ||
* ```ts | ||
* import { makeSTXTokenTransfer, transactionToHex } from '@stacks/transactions'; | ||
* | ||
* const transaction = makeSTXTokenTransfer({ ... }); | ||
* const hex = transactionToHex(transaction); | ||
* ``` | ||
*/ | ||
export function transactionToHex(transaction: StacksTransaction): string { | ||
return bytesToHex(transaction.serialize()); | ||
} |
@@ -157,2 +157,3 @@ import { | ||
): Address { | ||
// todo: `next` refactor to `requiredSignatures`, and opts object | ||
if (publicKeys.length === 0) { | ||
@@ -168,7 +169,9 @@ throw Error('Invalid number of public keys'); | ||
if (hashMode === AddressHashMode.SerializeP2WPKH || hashMode === AddressHashMode.SerializeP2WSH) { | ||
for (let i = 0; i < publicKeys.length; i++) { | ||
if (!isCompressed(publicKeys[i])) { | ||
throw Error('Public keys must be compressed for segwit'); | ||
} | ||
if ( | ||
hashMode === AddressHashMode.SerializeP2WPKH || | ||
hashMode === AddressHashMode.SerializeP2WSH || | ||
hashMode === AddressHashMode.SerializeP2WSHNonSequential | ||
) { | ||
if (!publicKeys.every(isCompressed)) { | ||
throw Error('Public keys must be compressed for segwit'); | ||
} | ||
@@ -183,4 +186,6 @@ } | ||
case AddressHashMode.SerializeP2SH: | ||
case AddressHashMode.SerializeP2SHNonSequential: | ||
return addressFromVersionHash(version, hashP2SH(numSigs, publicKeys.map(serializePublicKey))); | ||
case AddressHashMode.SerializeP2WSH: | ||
case AddressHashMode.SerializeP2WSHNonSequential: | ||
return addressFromVersionHash( | ||
@@ -328,2 +333,3 @@ version, | ||
// todo: `next` refactor for inversion of control | ||
export function deserializeLPList( | ||
@@ -330,0 +336,0 @@ bytesReader: BytesReader, |
@@ -37,2 +37,3 @@ import { ripemd160 } from '@noble/hashes/ripemd160'; | ||
// todo: remove this function and instead delete param without clone (if possible)? | ||
export function omit<T, K extends keyof any>(obj: T, prop: K): Omit<T, K> { | ||
@@ -39,0 +40,0 @@ const clone = cloneDeep(obj); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
1910657
265
18977