-
Notifications
You must be signed in to change notification settings - Fork 274
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs(blockapis): improve documentation for UtxoApi test suite
Issue: BTC-1084
- Loading branch information
1 parent
ee1341e
commit b650728
Showing
2 changed files
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import * as assert from 'assert'; | ||
import { UtxoPsbt } from '../../UtxoPsbt'; | ||
import { isTriple, Triple } from '../../types'; | ||
import { BIP32Factory, BIP32Interface } from 'bip32'; | ||
import { ecc as eccLib } from '../../../noble_ecc'; | ||
import { ParsedScriptType2Of3 } from '../../parseInput'; | ||
import { Network } from '../../../networks'; | ||
import { createOutputScript2of3, ScriptType2Of3 } from '../../outputScripts'; | ||
import { getPsbtInputScriptType } from '../Psbt'; | ||
import { createTransactionFromBuffer } from '../../transaction'; | ||
|
||
/** | ||
* Retrieves the root BIP32 keys from a PSBT's global xpubs. | ||
* Asserts that there are exactly three keys (or none), as expected for a 2-of-3 multisig setup. | ||
* @param psbt - The PSBT to extract root nodes from. | ||
* @returns An array of the three root BIP32 keys, or undefined if none are present. | ||
*/ | ||
export function getMultiSigRootNodes(psbt: UtxoPsbt): Triple<BIP32Interface> | undefined { | ||
const bip32s = psbt.data.globalMap.globalXpub?.map((xpub) => | ||
BIP32Factory(eccLib).fromBase58(bs58check.encode(xpub.extendedPubkey)) | ||
); | ||
assert(!bip32s || isTriple(bip32s), `Invalid globalXpubs in PSBT. Expected 3 or none. Got ${bip32s?.length}`); | ||
return bip32s; | ||
} | ||
|
||
/** | ||
* Maps a parsed 2-of-3 script type to its corresponding ScriptType2Of3 array. | ||
* Used to handle the various script types a PSBT input or output can be part of. | ||
* @param parsedScriptType - The parsed script type from a PSBT input/output. | ||
* @returns An array of ScriptType2Of3 based on the parsedScriptType. | ||
*/ | ||
export function toScriptType2Of3s(parsedScriptType: ParsedScriptType2Of3): ScriptType2Of3[] { | ||
return parsedScriptType === 'taprootScriptPathSpend' | ||
? ['p2trMusig2', 'p2tr'] | ||
: parsedScriptType === 'taprootKeyPathSpend' | ||
? ['p2trMusig2'] | ||
: [parsedScriptType]; | ||
} | ||
|
||
/** | ||
* Checks if a specific permutation of public keys matches the provided scriptPubKey. | ||
* Utilizes script type conversion and script creation to verify the match. | ||
* @param params - An object containing the required parameters for script matching. | ||
* @returns True if the scriptPubKey matches the constructed script for the given permutation and type. | ||
*/ | ||
function matchesScript({ | ||
publicKeys, | ||
perm, | ||
scriptPubKey, | ||
parsedScriptType, | ||
network, | ||
}: { | ||
publicKeys: Buffer[]; | ||
perm: Triple<number>; | ||
scriptPubKey: Buffer; | ||
parsedScriptType: ParsedScriptType2Of3; | ||
network: Network; | ||
}): boolean { | ||
const orderedPublicKeys: Triple<Buffer> = [publicKeys[perm[0]], publicKeys[perm[1]], publicKeys[perm[2]]]; | ||
const scriptTypes = toScriptType2Of3s(parsedScriptType); | ||
return scriptTypes.some((scriptType) => | ||
createOutputScript2of3(orderedPublicKeys, scriptType, network).scriptPubKey.equals(scriptPubKey) | ||
); | ||
} | ||
|
||
/** | ||
* Determines the correct order of public keys for a 2-of-3 multisig input based on the scriptPubKey. | ||
* It iterates through all possible permutations to find a match. | ||
* @param publicKeys - The public keys involved in the multisig setup. | ||
* @param scriptPubKey - The scriptPubKey from the PSBT input to match against. | ||
* @param parsedScriptType - The type of script the PSBT input is using. | ||
* @param network - The Bitcoin network the PSBT is for. | ||
* @param ordered - Specifies if the provided public keys are already ordered. | ||
* @returns An ordered array of indices representing the correct order of public keys. | ||
*/ | ||
function determineAssertedOrder({ | ||
publicKeys, | ||
scriptPubKey, | ||
parsedScriptType, | ||
network, | ||
ordered, | ||
}: { | ||
publicKeys: Triple<Buffer>; | ||
scriptPubKey: Buffer; | ||
parsedScriptType: ParsedScriptType2Of3; | ||
network: Network; | ||
ordered: boolean; | ||
}): Triple<number> { | ||
const permutations: Array<Triple<number>> = ordered | ||
? [[0, 1, 2]] | ||
: [ | ||
[0, 1, 2], | ||
[0, 2, 1], | ||
[1, 0, 2], | ||
[1, 2, 0], | ||
[2, 0, 1], | ||
[2, 1, 0], | ||
]; | ||
|
||
const order = permutations.find((perm) => | ||
matchesScript({ publicKeys, perm, scriptPubKey, parsedScriptType, network }) | ||
); | ||
assert(order, 'Could not determine order of public keys of multi sig input'); | ||
return order; | ||
} | ||
|
||
/** | ||
* Extracts multi-sig related information from a PSBT, necessary for root node ordering. | ||
* This includes script type, scriptPubKey, and derivation path for the first non-p2shP2pk input. | ||
* @param psbt - The PSBT to extract information from. | ||
* @returns An object containing the extracted details or undefined if not found. | ||
*/ | ||
function getMultiSigDetailsForSortRootNodes(psbt: UtxoPsbt): { | ||
parsedScriptType: ParsedScriptType2Of3; | ||
scriptPubKey: Buffer; | ||
derivationPath: string; | ||
} { | ||
const txInputs = psbt.txInputs; | ||
for (let i = 0; i < psbt.data.inputs.length; i++) { | ||
const input = psbt.data.inputs[i]; | ||
const parsedScriptType = getPsbtInputScriptType(input); | ||
if (parsedScriptType === 'p2shP2pk') { | ||
continue; | ||
} | ||
|
||
const prevOutIndex = txInputs[i].index; | ||
const scriptPubKey = | ||
input.witnessUtxo?.script ?? | ||
(input.nonWitnessUtxo | ||
? createTransactionFromBuffer(input.nonWitnessUtxo, psbt.network, { amountType: 'bigint' }).outs[prevOutIndex] | ||
.script | ||
: undefined); | ||
assert(scriptPubKey, 'Input scriptPubKey can not be found'); | ||
|
||
const bip32Dv = input?.bip32Derivation ?? input?.tapBip32Derivation; | ||
assert(bip32Dv?.length, 'Input Bip32Derivation can not be found'); | ||
const derivationPath = bip32Dv[0].path; | ||
|
||
return { parsedScriptType, scriptPubKey, derivationPath }; | ||
} | ||
throw new Error('No multi sig input found'); | ||
} | ||
|
||
/** | ||
* Orders the root nodes (BIP32 keys) canonically based on a PSBT's multi-sig script. | ||
* This is used to ensure consistent ordering of keys across PSBT operations. | ||
* @param psbt - The PSBT to order root nodes for. | ||
* @param rootNodes - Optionally, a specific set of root nodes to order if no PSBT globalXpub is provided. | ||
* @param ordered - Specifies if the provided rootNodes are already ordered. | ||
* @returns An ordered array of Triple<BIP32Interface>, representing the canonically ordered root nodes. | ||
*/ | ||
export function getAssertedCanonicalOrderedRootNodes(psbt: UtxoPsbt): Triple<BIP32Interface> { | ||
const unorderedRootNodes = getMultiSigRootNodes(psbt); | ||
assert(unorderedRootNodes, 'Either rootNodes or PSBT globalXpub must be provided'); | ||
|
||
const { parsedScriptType, scriptPubKey, derivationPath } = getMultiSigDetailsForSortRootNodes(psbt); | ||
|
||
const publicKeys = unorderedRootNodes.map( | ||
(rootNode) => rootNode.derivePath(derivationPath).publicKey | ||
) as Triple<Buffer>; | ||
|
||
const order = determineAssertedOrder({ | ||
publicKeys, | ||
scriptPubKey, | ||
parsedScriptType, | ||
network: psbt.network, | ||
ordered: false, | ||
}); | ||
return order.map((i) => unorderedRootNodes[i]) as Triple<BIP32Interface>; | ||
} |