Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: get input data based on address type #74

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 92 additions & 55 deletions src/psbt.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import {Psbt, Transaction} from "bitcoinjs-lib";
import { reverseBuffer } from 'bitcoinjs-lib/src/bufferutils';
import {toHexString} from './utils';
import { Psbt, Transaction } from "bitcoinjs-lib";
import { reverseBuffer } from "bitcoinjs-lib/src/bufferutils";
import { toHexString } from "./utils";
import {
generateMultisigFromHex,
multisigAddressType,
multisigBraidDetails,
multisigRedeemScript,
multisigWitnessScript,
} from './multisig';
import {bip32PathToSequence} from './paths';
import BigNumber from 'bignumber.js';
import {P2SH} from './p2sh';
import {P2WSH} from './p2wsh';
import {P2SH_P2WSH} from './p2sh_p2wsh';
import {generateBip32DerivationByIndex, generateBraid} from './braid';
import {networkData} from './networks';
} from "./multisig";
import { bip32PathToSequence } from "./paths";
import BigNumber from "bignumber.js";
import { P2SH } from "./p2sh";
import { P2WSH } from "./p2wsh";
import { P2SH_P2WSH } from "./p2sh_p2wsh";
import { generateBip32DerivationByIndex, generateBraid } from "./braid";
import { networkData } from "./networks";

/**
* This module provides functions for interacting with PSBTs, see BIP174
Expand Down Expand Up @@ -98,7 +98,7 @@ export function autoLoadPSBT(psbtFromFile, options) {
* {masterFingerprint: Buffer('1533..', 'hex'), path: "m/45'/0/0/0/0", pubkey: Buffer.from("02...", 'hex')}
* }
*/
function getBip32Derivation(multisig, index= 0) {
function getBip32Derivation(multisig, index = 0) {
// Already have one, return it
if (multisig.bip32Derivation) {
return multisig.bip32Derivation;
Expand All @@ -110,7 +110,7 @@ function getBip32Derivation(multisig, index= 0) {
config.addressType,
config.extendedPublicKeys,
config.requiredSigners,
config.index,
config.index
);
return generateBip32DerivationByIndex(braid, index);
}
Expand All @@ -124,7 +124,9 @@ function getBip32Derivation(multisig, index= 0) {
function psbtInputDerivation(input) {
// Multi-address inputs will have different bip32Derivations per address (index/path),
// so specify the index ... If the input is missing a path, assume you want index = 0.
const index = input.bip32Path ? bip32PathToSequence(input.bip32Path).slice(-1)[0] : 0;
const index = input.bip32Path
? bip32PathToSequence(input.bip32Path).slice(-1)[0]
: 0;
return getBip32Derivation(input.multisig, index);
}

Expand Down Expand Up @@ -200,13 +202,20 @@ export function psbtInputFormatter(input) {
// For SegWit inputs, you need an object with the output script buffer and output value
const witnessUtxo = getWitnessOutputScriptFromInput(input);
// For non-SegWit inputs, you must pass the full transaction buffer
const nonWitnessUtxo = Buffer.from(input.transactionHex, 'hex');
const nonWitnessUtxo = Buffer.from(input.transactionHex, "hex");

// FIXME - this makes the assumption that the funding transaction used the same transaction type as the current input
// we dont have isSegWit info on our inputs at the moment, so we don't know for sure.
// we don't have isSegWit info on our inputs at the moment, so we don't know for sure.
// This assumption holds in our fixtures, but it may need to be remedied in the future.
const isSegWit = multisigWitnessScript(input.multisig) !== null;
const utxoToVerify = isSegWit ? {witnessUtxo} : {nonWitnessUtxo};
const addressType = multisigAddressType(input.multisig);

// The PSBT class in bitcoinjs-lib won't accept both nonWitnessUxo and witnessUtxo
// but ledger v2 needs nonWitnessUtxo data in the PSBT for any transactions that aren't
// native segwit. So we need to pick which one to give based on the address type
// https://github.com/bitcoinjs/bitcoinjs-lib/issues/1595
const utxoToVerify =
addressType === P2WSH ? { witnessUtxo } : { nonWitnessUtxo };

Comment on lines +210 to +218
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is basically the only relevant change

const multisigScripts = psbtMultisigLock(input.multisig);

const bip32Derivation = psbtInputDerivation(input);
Expand Down Expand Up @@ -250,7 +259,7 @@ export function psbtOutputFormatter(output) {
address: output.address,
value: BigNumber(output.amountSats).toNumber(),
...output, // the output may have come in already decorated with bip32Derivation/multisigScripts
}
};
}

/**
Expand All @@ -266,17 +275,21 @@ function getUnchainedInputsFromPSBT(network, addressType, psbt) {
const dataInput = psbt.data.inputs[index];

// FIXME - this is where we're currently only handling P2SH correctly
const fundingTxHex = dataInput.nonWitnessUtxo.toString('hex');
const fundingTxHex = dataInput.nonWitnessUtxo.toString("hex");
const fundingTx = Transaction.fromHex(fundingTxHex);
const multisig = generateMultisigFromHex(network, addressType, dataInput.redeemScript.toString('hex'));
const multisig = generateMultisigFromHex(
network,
addressType,
dataInput.redeemScript.toString("hex")
);

return {
amountSats: fundingTx.outs[input.index].value,
index: input.index,
transactionHex: fundingTxHex,
txid: reverseBuffer(input.hash).toString('hex'),
txid: reverseBuffer(input.hash).toString("hex"),
multisig,
}
};
});
}

Expand All @@ -287,7 +300,7 @@ function getUnchainedInputsFromPSBT(network, addressType, psbt) {
* @return {Object[]} unchained multisig transaction outputs array
*/
function getUnchainedOutputsFromPSBT(psbt) {
return psbt.txOutputs.map(output => ({
return psbt.txOutputs.map((output) => ({
address: output.address,
amountSats: output.value,
}));
Expand All @@ -301,16 +314,18 @@ function getUnchainedOutputsFromPSBT(psbt) {
* @return {Object[]} bip32Derivations - array of signing bip32Derivation objects
*/
function filterRelevantBip32Derivations(psbt, signingKeyDetails) {
return psbt.data.inputs.map(input => {
const bip32Derivation = input.bip32Derivation.filter(b32d => b32d.path.startsWith(signingKeyDetails.path) &&
b32d.masterFingerprint.toString('hex') === signingKeyDetails.xfp
return psbt.data.inputs.map((input) => {
const bip32Derivation = input.bip32Derivation.filter(
(b32d) =>
b32d.path.startsWith(signingKeyDetails.path) &&
b32d.masterFingerprint.toString("hex") === signingKeyDetails.xfp
);

if (!bip32Derivation.length) {
throw new Error("Signing key details not included in PSBT");
}
return bip32Derivation[0];
})
});
}

/**
Expand All @@ -331,8 +346,10 @@ function filterRelevantBip32Derivations(psbt, signingKeyDetails) {
* }
*/
export function translatePSBT(network, addressType, psbt, signingKeyDetails) {
if (addressType !== P2SH) throw new Error("Unsupported addressType -- only P2SH is supported right now");
let localPSBT = autoLoadPSBT(psbt, {network: networkData(network)});
if (addressType !== P2SH) throw new Error(
"Unsupported addressType -- only P2SH is supported right now"
);
let localPSBT = autoLoadPSBT(psbt, { network: networkData(network) });
if (localPSBT === null) return null;

// The information we need to provide proper unchained-wallets style objects to the supported
Expand All @@ -343,18 +360,25 @@ export function translatePSBT(network, addressType, psbt, signingKeyDetails) {
// We'll do that in the functions below.

// First, we check that we actually do have any inputs to sign:
const bip32Derivations = filterRelevantBip32Derivations(localPSBT, signingKeyDetails);
const bip32Derivations = filterRelevantBip32Derivations(
localPSBT,
signingKeyDetails
);

// The shape of these return objects are specific to existing code
// in unchained-wallets for signing with Trezor and Ledger devices.
const unchainedInputs = getUnchainedInputsFromPSBT(network, addressType, localPSBT);
const unchainedInputs = getUnchainedInputsFromPSBT(
network,
addressType,
localPSBT
);
const unchainedOutputs = getUnchainedOutputsFromPSBT(localPSBT);

return {
unchainedInputs,
unchainedOutputs,
bip32Derivations,
}
};
}

/**
Expand All @@ -376,9 +400,9 @@ function addSignatureToPSBT(psbt, inputIndex, pubkey, signature) {
{
pubkey,
signature,
}
]
psbt.data.updateInput(inputIndex, {partialSig})
},
];
psbt.data.updateInput(inputIndex, { partialSig });
if (!psbt.validateSignaturesOfInput(inputIndex, pubkey)) throw new Error("One or more invalid signatures.");
return psbt;
}
Expand All @@ -399,14 +423,20 @@ function addSignatureToPSBT(psbt, inputIndex, pubkey, signature) {
* @return {null|string} - partially signed PSBT in Base64
*/
export function addSignaturesToPSBT(network, psbt, pubkeys, signatures) {
let psbtWithSignatures = autoLoadPSBT(psbt, {network: networkData(network)});
let psbtWithSignatures = autoLoadPSBT(psbt, {
network: networkData(network),
});
if (psbtWithSignatures === null) return null;

signatures.forEach((sig, idx) => {
const pubkey = pubkeys[idx];
psbtWithSignatures = addSignatureToPSBT(psbtWithSignatures, idx, pubkey, sig);
}
)
psbtWithSignatures = addSignatureToPSBT(
psbtWithSignatures,
idx,
pubkey,
sig
);
});
return psbtWithSignatures.toBase64();
}

Expand All @@ -419,12 +449,10 @@ export function addSignaturesToPSBT(network, psbt, pubkeys, signatures) {
*/

function getNumSigners(psbt) {
const partialSignatures = (
psbt &&
psbt.data &&
psbt.data.inputs &&
psbt.data.inputs[0]
) ? psbt.data.inputs[0].partialSig : undefined;
const partialSignatures =
psbt && psbt.data && psbt.data.inputs && psbt.data.inputs[0]
? psbt.data.inputs[0].partialSig
: undefined;
return partialSignatures === undefined ? 0 : partialSignatures.length;
}

Expand Down Expand Up @@ -455,18 +483,24 @@ export function parseSignaturesFromPSBT(psbtFromFile) {
const numSigners = getNumSigners(psbt);

const signatureSet = {};
let pubKey = '';
let pubKey = "";
const inputs = psbt.data.inputs;
// Find signatures in the PSBT
if (numSigners >= 1) {
// return array of arrays of signatures
for (let i = 0; i < inputs.length; i++) {
for (let j = 0; j < numSigners; j++) {
pubKey = toHexString(Array.prototype.slice.call(inputs[i].partialSig[j].pubkey));
pubKey = toHexString(
Array.prototype.slice.call(inputs[i].partialSig[j].pubkey)
);
if (pubKey in signatureSet) {
signatureSet[pubKey].push(inputs[i].partialSig[j].signature.toString("hex"));
signatureSet[pubKey].push(
inputs[i].partialSig[j].signature.toString("hex")
);
} else {
signatureSet[pubKey] = [inputs[i].partialSig[j].signature.toString("hex")];
signatureSet[pubKey] = [
inputs[i].partialSig[j].signature.toString("hex"),
];
}
}
}
Expand All @@ -476,11 +510,10 @@ export function parseSignaturesFromPSBT(psbtFromFile) {
return signatureSet;
}


/**
* Extracts signatures in order of inputs and returns as array (or array of arrays if multiple signature sets)
*
* @param {String} psbtFromFile - base64 or hex
* @param {String} psbtFromFile - base64 or hex
* @returns {Object} returns an array of arrays of ordered signatures or an array of signatures if only 1 signer
*
*/
Expand All @@ -490,15 +523,19 @@ export function parseSignatureArrayFromPSBT(psbtFromFile) {

const numSigners = getNumSigners(psbt);

const signatureArrays = Array.from(Array(numSigners).fill().map(() => []));
const signatureArrays = Array.from(
Array(numSigners)
.fill()
.map(() => [])
);

const {inputs} = psbt.data;
const { inputs } = psbt.data;

if (numSigners >= 1) {
for (let i = 0; i < inputs.length; i += 1) {
for (let j = 0; j < numSigners; j += 1) {
let signature = inputs[i].partialSig[j].signature.toString("hex");
signatureArrays[j].push(signature)
signatureArrays[j].push(signature);
}
}
} else {
Expand Down