PHP WebShell
Текущая директория: /opt/BitGoJS/modules/abstract-utxo/src
Просмотр файла: sign.ts
import * as utxolib from '@bitgo/utxo-lib';
import debugLib from 'debug';
import { isReplayProtectionUnspent } from './replayProtection';
const debug = debugLib('bitgo:v2:utxo');
const { isWalletUnspent, signInputWithUnspent, toOutput } = utxolib.bitgo;
type Unspent<TNumber extends number | bigint = number> = utxolib.bitgo.Unspent<TNumber>;
type RootWalletKeys = utxolib.bitgo.RootWalletKeys;
type PsbtParsedScriptTypes =
| 'p2sh'
| 'p2wsh'
| 'p2shP2wsh'
| 'p2shP2pk'
| 'taprootKeyPathSpend'
| 'taprootScriptPathSpend';
export class InputSigningError<TNumber extends number | bigint = number> extends Error {
static expectedWalletUnspent<TNumber extends number | bigint>(
inputIndex: number,
unspent: Unspent<TNumber> | { id: string }
): InputSigningError<TNumber> {
return new InputSigningError(inputIndex, unspent, `not a wallet unspent, not a replay protection unspent`);
}
constructor(
public inputIndex: number,
public unspent: Unspent<TNumber> | { id: string },
public reason: Error | string
) {
super(`signing error at input ${inputIndex}: unspentId=${unspent.id}: ${reason}`);
}
}
export class TransactionSigningError<TNumber extends number | bigint = number> extends Error {
constructor(signErrors: InputSigningError<TNumber>[], verifyError: InputSigningError<TNumber>[]) {
super(
`sign errors at inputs: [${signErrors.join(',')}], ` +
`verify errors at inputs: [${verifyError.join(',')}], see log for details`
);
}
}
/**
* Sign all inputs of a psbt and verify signatures after signing.
* Collects and logs signing errors and verification errors, throws error in the end if any of them
* failed.
*
* If it is the last signature, finalize and extract the transaction from the psbt.
*
* This function mirrors signAndVerifyWalletTransaction, but is used for signing PSBTs instead of
* using TransactionBuilder
*
* @param psbt
* @param signerKeychain
* @param isLastSignature
*/
export function signAndVerifyPsbt(
psbt: utxolib.bitgo.UtxoPsbt,
signerKeychain: utxolib.BIP32Interface,
{
isLastSignature,
allowNonSegwitSigningWithoutPrevTx,
}: { isLastSignature: boolean; allowNonSegwitSigningWithoutPrevTx?: boolean }
): utxolib.bitgo.UtxoPsbt | utxolib.bitgo.UtxoTransaction<bigint> {
const txInputs = psbt.txInputs;
const outputIds: string[] = [];
const scriptTypes: PsbtParsedScriptTypes[] = [];
const signErrors: InputSigningError<bigint>[] = psbt.data.inputs
.map((input, inputIndex: number) => {
const outputId = utxolib.bitgo.formatOutputId(utxolib.bitgo.getOutputIdForInput(txInputs[inputIndex]));
outputIds.push(outputId);
const { scriptType } = utxolib.bitgo.parsePsbtInput(input);
scriptTypes.push(scriptType);
if (scriptType === 'p2shP2pk') {
debug('Skipping signature for input %d of %d (RP input?)', inputIndex + 1, psbt.data.inputs.length);
return;
}
try {
utxolib.bitgo.withUnsafeNonSegwit(
psbt,
() => psbt.signInputHD(inputIndex, signerKeychain),
!!allowNonSegwitSigningWithoutPrevTx
);
debug('Successfully signed input %d of %d', inputIndex + 1, psbt.data.inputs.length);
} catch (e) {
return new InputSigningError<bigint>(inputIndex, { id: outputId }, e);
}
})
.filter((e): e is InputSigningError<bigint> => e !== undefined);
const verifyErrors: InputSigningError<bigint>[] = psbt.data.inputs
.map((input, inputIndex) => {
const scriptType = scriptTypes[inputIndex];
if (scriptType === 'p2shP2pk') {
debug(
'Skipping input signature %d of %d (unspent from replay protection address which is platform signed only)',
inputIndex + 1,
psbt.data.inputs.length
);
return;
}
const outputId = outputIds[inputIndex];
try {
if (
!utxolib.bitgo.withUnsafeNonSegwit(
psbt,
() => psbt.validateSignaturesOfInputHD(inputIndex, signerKeychain),
!!allowNonSegwitSigningWithoutPrevTx
)
) {
return new InputSigningError(inputIndex, { id: outputId }, new Error(`invalid signature`));
}
} catch (e) {
debug('Invalid signature');
return new InputSigningError<bigint>(inputIndex, { id: outputId }, e);
}
})
.filter((e): e is InputSigningError<bigint> => e !== undefined);
if (signErrors.length || verifyErrors.length) {
throw new TransactionSigningError(signErrors, verifyErrors);
}
if (isLastSignature) {
psbt.finalizeAllInputs();
return psbt.extractTransaction();
}
return psbt;
}
/**
* Sign all inputs of a wallet transaction and verify signatures after signing.
* Collects and logs signing errors and verification errors, throws error in the end if any of them
* failed.
*
* @param transaction - wallet transaction (builder) to be signed
* @param unspents - transaction unspents
* @param walletSigner - signing parameters
* @param isLastSignature - Returns full-signed transaction when true. Builds half-signed when false.
*/
export function signAndVerifyWalletTransaction<TNumber extends number | bigint>(
transaction: utxolib.bitgo.UtxoTransaction<TNumber> | utxolib.bitgo.UtxoTransactionBuilder<TNumber>,
unspents: Unspent<TNumber>[],
walletSigner: utxolib.bitgo.WalletUnspentSigner<RootWalletKeys>,
{ isLastSignature }: { isLastSignature: boolean }
): utxolib.bitgo.UtxoTransaction<TNumber> {
const network = transaction.network as utxolib.Network;
const prevOutputs = unspents.map((u) => toOutput(u, network));
let txBuilder: utxolib.bitgo.UtxoTransactionBuilder<TNumber>;
if (transaction instanceof utxolib.bitgo.UtxoTransaction) {
txBuilder = utxolib.bitgo.createTransactionBuilderFromTransaction<TNumber>(transaction, prevOutputs);
if (transaction.ins.length !== unspents.length) {
throw new Error(`transaction inputs must match unspents`);
}
} else if (transaction instanceof utxolib.bitgo.UtxoTransactionBuilder) {
txBuilder = transaction;
} else {
throw new Error(`must pass UtxoTransaction or UtxoTransactionBuilder`);
}
const signErrors: InputSigningError<TNumber>[] = unspents
.map((unspent: Unspent<TNumber>, inputIndex: number) => {
if (isReplayProtectionUnspent<TNumber>(unspent, network)) {
debug('Skipping signature for input %d of %d (RP input?)', inputIndex + 1, unspents.length);
return;
}
if (!isWalletUnspent<TNumber>(unspent)) {
return InputSigningError.expectedWalletUnspent<TNumber>(inputIndex, unspent);
}
try {
signInputWithUnspent<TNumber>(txBuilder, inputIndex, unspent, walletSigner);
debug('Successfully signed input %d of %d', inputIndex + 1, unspents.length);
} catch (e) {
return new InputSigningError<TNumber>(inputIndex, unspent, e);
}
})
.filter((e): e is InputSigningError<TNumber> => e !== undefined);
const signedTransaction = isLastSignature ? txBuilder.build() : txBuilder.buildIncomplete();
const verifyErrors: InputSigningError<TNumber>[] = signedTransaction.ins
.map((input, inputIndex) => {
const unspent = unspents[inputIndex] as Unspent<TNumber>;
if (isReplayProtectionUnspent<TNumber>(unspent, network)) {
debug(
'Skipping input signature %d of %d (unspent from replay protection address which is platform signed only)',
inputIndex + 1,
unspents.length
);
return;
}
if (!isWalletUnspent<TNumber>(unspent)) {
return InputSigningError.expectedWalletUnspent<TNumber>(inputIndex, unspent);
}
try {
const publicKey = walletSigner.deriveForChainAndIndex(unspent.chain, unspent.index).signer.publicKey;
if (
!utxolib.bitgo.verifySignatureWithPublicKey<TNumber>(signedTransaction, inputIndex, prevOutputs, publicKey)
) {
return new InputSigningError(inputIndex, unspent, new Error(`invalid signature`));
}
} catch (e) {
debug('Invalid signature');
return new InputSigningError<TNumber>(inputIndex, unspent, e);
}
})
.filter((e): e is InputSigningError<TNumber> => e !== undefined);
if (signErrors.length || verifyErrors.length) {
throw new TransactionSigningError(signErrors, verifyErrors);
}
return signedTransaction;
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!