Browse Source

Fix setup for typescript

master
junderw 5 years ago
parent
commit
00bb116a33
No known key found for this signature in database GPG Key ID: A9273B5AD3E47B45
  1. 3
      .gitignore
  2. 12
      .travis.yml
  3. 2
      LICENSE
  4. 29
      jest.json
  5. 4473
      package-lock.json
  6. 41
      package.json
  7. 4
      src/index.d.ts
  8. 299
      src/index.js
  9. 0
      test/index.js
  10. 10
      ts_src/index.spec.ts
  11. 158
      ts_src/index.ts
  12. 39
      tsconfig.json
  13. 10
      tslint.json

3
.gitignore

@ -1,4 +1,3 @@
node_modules node_modules
.nyc_output
coverage coverage
.idea package-lock.json

12
.travis.yml

@ -1,19 +1,15 @@
sudo: false sudo: false
language: node_js language: node_js
node_js: node_js:
- "lts/*"
- "9"
- "10" - "10"
- "13"
- "lts/*"
matrix: matrix:
include: include:
- node_js: "lts/*"
env: TEST_SUITE=format:ci
- node_js: "lts/*" - node_js: "lts/*"
env: TEST_SUITE=gitdiff:ci env: TEST_SUITE=gitdiff:ci
- node_js: "lts/*"
env: TEST_SUITE=lint
- node_js: "lts/*" - node_js: "lts/*"
env: TEST_SUITE=coverage env: TEST_SUITE=coverage
env: env:
- TEST_SUITE=unit - TEST_SUITE=test
script: npm run-script $TEST_SUITE script: npm run $TEST_SUITE

2
LICENSE

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2011-2018 bitcoinjs-lib contributors Copyright (c) 2020 bitcoinjs-lib contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

29
jest.json

@ -0,0 +1,29 @@
{
"moduleFileExtensions": [
"ts",
"js",
"json"
],
"transform": {
"^.+\\.ts$": "ts-jest"
},
"testRegex": "/ts_src/.*\\.spec\\.ts$",
"testURL": "http://localhost/",
"coverageThreshold": {
"global": {
"statements": 0,
"branches": 0,
"functions": 0,
"lines": 0
}
},
"collectCoverageFrom": [
"ts_src/**/*.ts",
"!**/node_modules/**"
],
"coverageReporters": [
"lcov",
"text"
],
"verbose": true
}

4473
package-lock.json

File diff suppressed because it is too large

41
package.json

@ -10,44 +10,41 @@
"btcpayserver" "btcpayserver"
], ],
"main": "./src/index.js", "main": "./src/index.js",
"types": "./types/index.d.ts", "types": "./src/index.d.ts",
"engines": {
"node": ">=6.0.0"
},
"scripts": { "scripts": {
"build": "tsc -p ./tsconfig.json", "build": "npm run clean && tsc -p tsconfig.json && npm run formatjs",
"coverage-report": "npm run build && npm run nobuild:coverage-report", "clean": "rm -rf src",
"coverage": "npm run build && npm run nobuild:coverage", "coverage": "npm run unit -- --coverage",
"format": "npm run prettier -- --write", "format": "npm run prettier -- --write",
"formatjs": "npm run prettierjs -- --write > /dev/null 2>&1",
"format:ci": "npm run prettier -- --check", "format:ci": "npm run prettier -- --check",
"gitdiff:ci": "npm run build && git diff --exit-code", "gitdiff": "git diff --exit-code",
"gitdiff:ci": "npm run build && npm run gitdiff",
"lint": "tslint -p tsconfig.json -c tslint.json", "lint": "tslint -p tsconfig.json -c tslint.json",
"nobuild:coverage-report": "nyc report --reporter=lcov", "prepublishOnly": "npm run test && npm run gitdiff",
"nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 npm run nobuild:unit", "prettier": "prettier 'ts_src/**/*.ts' --ignore-path ./.prettierignore",
"nobuild:unit": "tape test/*.js", "prettierjs": "prettier 'src/**/*.js' --ignore-path ./.prettierignore",
"prettier": "prettier 'ts-src/**/*.ts' --ignore-path ./.prettierignore", "test": "npm run build && npm run format:ci && npm run lint && npm run unit",
"test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage", "unit": "jest --config=jest.json --runInBand"
"unit": "npm run build && npm run nobuild:unit"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/Kukks/payjoin-client-js.git" "url": "git+https://github.com/Kukks/payjoin-client-js.git"
}, },
"files": [ "files": [
"src", "src"
"types"
], ],
"dependencies": { "dependencies": {
"@types/node": "^13.13.0",
"bip174": "^1.0.1",
"bitcoinjs-lib": "^5.1.7" "bitcoinjs-lib": "^5.1.7"
}, },
"devDependencies": { "devDependencies": {
"nyc": "^15.0.0", "@types/jest": "^25.2.1",
"prettier": "1.16.4", "@types/node": "^13.13.0",
"tape": "^4.13.2", "jest": "^25.3.0",
"prettier": "^2.0.4",
"ts-jest": "^25.3.1",
"tslint": "^6.1.1", "tslint": "^6.1.1",
"typescript": "3.8.3" "typescript": "^3.8.3"
}, },
"author": "Andrew Camilleri (Kukks)", "author": "Andrew Camilleri (Kukks)",
"license": "MIT", "license": "MIT",

4
types/index.d.ts → src/index.d.ts

@ -1,5 +1,5 @@
import { Psbt } from 'bitcoinjs-lib'; import { Psbt } from 'bitcoinjs-lib';
declare type Nullable<T> = T | null; declare type Nullable<T> = T | null;
export declare function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall: (psbt: Psbt) => Promise<Nullable<Psbt>>): Promise<null | undefined>; export declare function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall: (psbt: Psbt) => Promise<Nullable<Psbt>>): Promise<void>;
export declare function requestPayjoin(psbt: Psbt, payjoinEndpoint: string): Promise<null | undefined>; export declare function requestPayjoin(psbt: Psbt, payjoinEndpoint: string): Promise<void>;
export {}; export {};

299
src/index.js

@ -1,88 +1,231 @@
"use strict"; 'use strict';
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, '__esModule', { value: true });
const bitcoinjs_lib_1 = require("bitcoinjs-lib"); const bitcoinjs_lib_1 = require('bitcoinjs-lib');
const payments_1 = require("bitcoinjs-lib/types/payments"); const bitcoinjs_lib_2 = require('bitcoinjs-lib');
async function requestPayjoinWithCustomRemoteCall(psbt, remoteCall) { async function requestPayjoinWithCustomRemoteCall(psbt, remoteCall) {
const clonedPsbt = psbt.clone(); const clonedPsbt = psbt.clone();
clonedPsbt.finalizeAllInputs(); clonedPsbt.finalizeAllInputs();
// We make sure we don't send unnecessary information to the receiver // We make sure we don't send unnecessary information to the receiver
for (let index = 0; index < clonedPsbt.inputCount; index++) { for (let index = 0; index < clonedPsbt.inputCount; index++) {
clonedPsbt.clearFinalizedInput(index); clonedPsbt.clearFinalizedInput(index);
} }
clonedPsbt.data.outputs.forEach(output => { clonedPsbt.data.outputs.forEach((output) => {
delete output.bip32Derivation; delete output.bip32Derivation;
});
delete clonedPsbt.data.globalMap.globalXpub;
const payjoinPsbt = await remoteCall(clonedPsbt);
if (!payjoinPsbt) throw new Error("We did not get the receiver's PSBT");
// no inputs were added?
if (clonedPsbt.inputCount <= payjoinPsbt.inputCount) {
throw new Error(
"There were less inputs than before in the receiver's PSBT",
);
}
if (
payjoinPsbt.data.globalMap.globalXpub &&
payjoinPsbt.data.globalMap.globalXpub.length > 0
) {
throw new Error(
"GlobalXPubs should not be included in the receiver's PSBT",
);
}
if (
hasKeypathInformationSet(payjoinPsbt.data.outputs) ||
hasKeypathInformationSet(payjoinPsbt.data.inputs)
) {
throw new Error(
"Keypath information should not be included in the receiver's PSBT",
);
}
const sanityResult = checkSanity(payjoinPsbt);
if (Object.keys(sanityResult).length > 0) {
throw new Error(
`Receiver's PSBT is insane: ${JSON.stringify(sanityResult)}`,
);
}
// We make sure we don't sign what should not be signed
for (let index = 0; index < payjoinPsbt.inputCount; index++) {
// check if input is Finalized
if (isFinalized(payjoinPsbt.data.inputs[index]))
payjoinPsbt.clearFinalizedInput(index);
}
for (let index = 0; index < payjoinPsbt.data.outputs.length; index++) {
const output = payjoinPsbt.data.outputs[index];
const outputLegacy = getGlobalTransaction(payjoinPsbt).outs[index];
// Make sure only our output has any information
delete output.bip32Derivation;
psbt.data.outputs.forEach((originalOutput) => {
// update the payjoin outputs
if (
outputLegacy.script.equals(
// TODO: what if output is P2SH or P2WSH or anything other than P2WPKH?
// Can we assume output will contain redeemScript and witnessScript?
// If so, we could decompile scriptPubkey, RS, and WS, and search for
// the pubkey and its hash160.
bitcoinjs_lib_2.payments.p2wpkh({
pubkey: originalOutput.bip32Derivation[0].pubkey,
}).output,
)
)
payjoinPsbt.updateOutput(index, originalOutput);
}); });
delete clonedPsbt.data.globalMap.globalXpub; }
const payjoinPsbt = await remoteCall(clonedPsbt); // TODO: check payjoinPsbt.version == psbt.version
if (!payjoinPsbt) // TODO: check payjoinPsbt.locktime == psbt.locktime
return null; // TODO: check payjoinPsbt.inputs where input belongs to us, that it is not finalized
// no inputs were added? // TODO: check payjoinPsbt.inputs where input belongs to us, that it is was included in psbt.inputs
if (clonedPsbt.inputCount <= payjoinPsbt.inputCount) { // TODO: check payjoinPsbt.inputs where input belongs to us, that its sequence has not changed from that of psbt.inputs
return null; // TODO: check payjoinPsbt.inputs where input is new, that it is finalized
// TODO: check payjoinPsbt.inputs where input is new, that it is the same type as all other inputs from psbt.inputs (all==P2WPKH || all = P2SH-P2WPKH)
// TODO: check psbt.inputs that payjoinPsbt.inputs contains them all
// TODO: check payjoinPsbt.inputs > psbt.inputs
// TODO: check that if spend amount of payjoinPsbt > spend amount of psbt:
// TODO: * check if the difference is due to adjusting fee to increase transaction size
}
exports.requestPayjoinWithCustomRemoteCall = requestPayjoinWithCustomRemoteCall;
async function requestPayjoin(psbt, payjoinEndpoint) {
return requestPayjoinWithCustomRemoteCall(psbt, (psbt1) =>
doRequest(psbt1, payjoinEndpoint),
);
}
exports.requestPayjoin = requestPayjoin;
function checkSanity(psbt) {
const result = {};
psbt.data.inputs.forEach((value, index) => {
const sanityResult = checkInputSanity(
value,
getGlobalTransaction(psbt).ins[index],
);
if (sanityResult.length > 0) {
result[index] = sanityResult;
}
});
return result;
}
function checkInputSanity(input, txInput) {
const errors = [];
if (isFinalized(input)) {
if (input.partialSig && input.partialSig.length > 0) {
errors.push('Input finalized, but partial sigs are not empty');
}
if (input.bip32Derivation && input.bip32Derivation.length > 0) {
errors.push('Input finalized, but hd keypaths are not empty');
}
if (input.sighashType) {
errors.push('Input finalized, but sighash type is not null');
}
if (input.redeemScript) {
errors.push('Input finalized, but redeem script is not null');
}
if (input.witnessScript) {
errors.push('Input finalized, but witness script is not null');
} }
// We make sure we don't sign things what should not be signed }
for (let index = 0; index < payjoinPsbt.inputCount; index++) { if (input.witnessUtxo && input.nonWitnessUtxo) {
// Is Finalized errors.push('witness utxo and non witness utxo simultaneously present');
if (payjoinPsbt.data.inputs[index].finalScriptSig !== undefined || }
payjoinPsbt.data.inputs[index].finalScriptWitness !== undefined) if (input.witnessScript && !input.witnessUtxo) {
payjoinPsbt.clearFinalizedInput(index); errors.push('witness script present but no witness utxo');
}
if (!input.finalScriptWitness && !input.witnessUtxo) {
errors.push('final witness script present but no witness utxo');
}
if (input.nonWitnessUtxo) {
// TODO: get hash
const prevOutTxId = input.nonWitnessUtxo;
let validOutpoint = true;
if (txInput.hash !== prevOutTxId) {
errors.push(
'non_witness_utxo does not match the transaction id referenced by the global transaction sign',
);
validOutpoint = false;
} }
for (let index = 0; index < payjoinPsbt.data.outputs.length; index++) { // @ts-ignore
const output = payjoinPsbt.data.outputs[index]; if (txInput.index >= input.nonWitnessUtxo.Outputs.length) {
// TODO: bitcoinjs-lib to expose outputs to Psbt class errors.push(
// instead of using private (JS has no private) attributes 'Global transaction referencing an out of bound output in non_witness_utxo',
);
validOutpoint = false;
}
if (input.redeemScript && validOutpoint) {
if (
// @ts-ignore
input.redeemScript.Hash.ScriptPubKey !==
// @ts-ignore
input.nonWitnessUtxo.Outputs[txInput.index].ScriptPubKey
)
errors.push(
'The redeem_script is not coherent with the scriptPubKey of the non_witness_utxo',
);
}
}
if (input.witnessUtxo) {
if (input.redeemScript) {
if (
// @ts-ignore
input.redeemScript.Hash.ScriptPubKey !== input.witnessUtxo.ScriptPubKey
)
errors.push(
'The redeem_script is not coherent with the scriptPubKey of the witness_utxo',
);
if (
input.witnessScript &&
input.redeemScript &&
// @ts-ignore // @ts-ignore
const outputLegacy = payjoinPsbt.__CACHE.__TX.outs[index]; PayToWitScriptHashTemplate.Instance.ExtractScriptPubKeyParameters(
// Make sure only the only our output have any information input.redeemScript,
delete output.bip32Derivation; ) !== input.witnessScript.WitHash
psbt.data.outputs.forEach(originalOutput => { )
// update the payjoin outputs errors.push(
if (outputLegacy.script.equals( 'witnessScript with witness UTXO does not match the redeemScript',
// TODO: what if output is P2SH or P2WSH or anything other than P2WPKH? );
// Can we assume output will contain redeemScript and witnessScript?
// If so, we could decompile scriptPubkey, RS, and WS, and search for
// the pubkey and its hash160.
payments_1.p2wpkh({
pubkey: originalOutput.bip32Derivation.pubkey,
}).output))
payjoinPsbt.updateOutput(index, originalOutput);
});
} }
// TODO: check payjoinPsbt.version == psbt.version }
// TODO: check payjoinPsbt.locktime == psbt.locktime // figure out how to port this lofic
// TODO: check payjoinPsbt.inputs where input belongs to us, that it is not finalized // if (input.witnessUtxo.ScriptPubKey is Script s)
// TODO: check payjoinPsbt.inputs where input belongs to us, that it is was included in psbt.inputs // {
// TODO: check payjoinPsbt.inputs where input belongs to us, that its sequence has not changed from that of psbt.inputs //
// TODO: check payjoinPsbt.inputs where input is new, that it is finalized // if (!s.IsScriptType(ScriptType.P2SH) && !s.IsScriptType(ScriptType.Witness))
// TODO: check payjoinPsbt.inputs where input is new, that it is the same type as all other inputs from psbt.inputs (all==P2WPKH || all = P2SH-P2WPKH) // errors.push('A Witness UTXO is provided for a non-witness input');
// TODO: check psbt.inputs that payjoinPsbt.inputs contains them all // if (s.IsScriptType(ScriptType.P2SH) && redeem_script is Script r && !r.IsScriptType(ScriptType.Witness))
// TODO: check payjoinPsbt.inputs > psbt.inputs // errors.push('A Witness UTXO is provided for a non-witness input');
// TODO: check that if spend amount of payjoinPsbt > spend amount of psbt: // }
// TODO: * check if the difference is due to adjusting fee to increase transaction size return errors;
} }
exports.requestPayjoinWithCustomRemoteCall = requestPayjoinWithCustomRemoteCall; function hasKeypathInformationSet(items) {
function requestPayjoin(psbt, payjoinEndpoint) { return (
return requestPayjoinWithCustomRemoteCall(psbt, psbt1 => doRequest(psbt1, payjoinEndpoint)); items.filter(
(value) => !!value.bip32Derivation && value.bip32Derivation.length > 0,
).length > 0
);
}
function isFinalized(input) {
return (
input.finalScriptSig !== undefined || input.finalScriptWitness !== undefined
);
}
function getGlobalTransaction(psbt) {
// TODO: bitcoinjs-lib to expose outputs to Psbt class
// instead of using private (JS has no private) attributes
// @ts-ignore
return psbt.__CACHE.__TX;
} }
exports.requestPayjoin = requestPayjoin;
function doRequest(psbt, payjoinEndpoint) { function doRequest(psbt, payjoinEndpoint) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!psbt) { if (!psbt) {
reject(); reject();
} }
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => { xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) if (xhr.readyState !== 4) return;
return; if (xhr.status >= 200 && xhr.status < 300) {
if (xhr.status >= 200 && xhr.status < 300) { resolve(bitcoinjs_lib_1.Psbt.fromHex(xhr.responseText));
resolve(bitcoinjs_lib_1.Psbt.fromHex(xhr.responseText)); } else {
} reject(xhr.responseText);
else { }
reject(xhr.responseText); };
} xhr.setRequestHeader('Content-Type', 'text/plain');
}; xhr.open('POST', payjoinEndpoint);
xhr.setRequestHeader('Content-Type', 'text/plain'); xhr.send(psbt.toHex());
xhr.open('POST', payjoinEndpoint); });
xhr.send(psbt.toHex());
});
} }

0
test/index.js

10
ts_src/index.spec.ts

@ -0,0 +1,10 @@
import { requestPayjoin, requestPayjoinWithCustomRemoteCall } from './index';
describe('requestPayjoin', () => {
it('should exist', () => {
expect(requestPayjoin).toBeDefined();
expect(typeof requestPayjoin).toBe('function');
expect(requestPayjoinWithCustomRemoteCall).toBeDefined();
expect(typeof requestPayjoinWithCustomRemoteCall).toBe('function');
});
});

158
ts-src/index.ts → ts_src/index.ts

@ -1,11 +1,24 @@
import { Psbt, Transaction } from 'bitcoinjs-lib'; import { Psbt, Transaction } from 'bitcoinjs-lib';
import { p2wpkh } from 'bitcoinjs-lib/types/payments'; import { payments } from 'bitcoinjs-lib';
import { Bip32Derivation, GlobalXpub, PsbtInput } from 'bip174/src/lib/interfaces'; import {
import { Input } from 'bitcoinjs-lib/types/transaction'; Bip32Derivation,
GlobalXpub,
PsbtInput,
} from 'bip174/src/lib/interfaces';
type Nullable<T> = T | null; type Nullable<T> = T | null;
interface TxInput {
hash: Buffer;
index: number;
script: Buffer;
sequence: number;
witness: Buffer[];
}
export async function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall: (psbt: Psbt) => Promise<Nullable<Psbt>>) { export async function requestPayjoinWithCustomRemoteCall(
psbt: Psbt,
remoteCall: (psbt: Psbt) => Promise<Nullable<Psbt>>,
): Promise<void> {
const clonedPsbt = psbt.clone(); const clonedPsbt = psbt.clone();
clonedPsbt.finalizeAllInputs(); clonedPsbt.finalizeAllInputs();
@ -13,29 +26,43 @@ export async function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall:
for (let index = 0; index < clonedPsbt.inputCount; index++) { for (let index = 0; index < clonedPsbt.inputCount; index++) {
clonedPsbt.clearFinalizedInput(index); clonedPsbt.clearFinalizedInput(index);
} }
clonedPsbt.data.outputs.forEach(output => { clonedPsbt.data.outputs.forEach((output): void => {
delete output.bip32Derivation; delete output.bip32Derivation;
}); });
delete clonedPsbt.data.globalMap.globalXpub; delete clonedPsbt.data.globalMap.globalXpub;
const payjoinPsbt = await remoteCall(clonedPsbt); const payjoinPsbt = await remoteCall(clonedPsbt);
if (!payjoinPsbt) throw new Error('We did not get the receiver\'s PSBT'); if (!payjoinPsbt) throw new Error("We did not get the receiver's PSBT");
// no inputs were added? // no inputs were added?
if (clonedPsbt.inputCount <= payjoinPsbt.inputCount) { if (clonedPsbt.inputCount <= payjoinPsbt.inputCount) {
throw new Error('There were less inputs than before in the receiver\'s PSBT'); throw new Error(
"There were less inputs than before in the receiver's PSBT",
);
} }
if (payjoinPsbt.data.globalMap.globalXpub && (payjoinPsbt.data.globalMap.globalXpub as GlobalXpub[]).length > 0) { if (
throw new Error('GlobalXPubs should not be included in the receiver\'s PSBT'); payjoinPsbt.data.globalMap.globalXpub &&
(payjoinPsbt.data.globalMap.globalXpub as GlobalXpub[]).length > 0
) {
throw new Error(
"GlobalXPubs should not be included in the receiver's PSBT",
);
} }
if (hasKeypathInformationSet(payjoinPsbt.data.outputs) || hasKeypathInformationSet(payjoinPsbt.data.inputs)) { if (
throw new Error(('Keypath information should not be included in the receiver\'s PSBT'); hasKeypathInformationSet(payjoinPsbt.data.outputs) ||
hasKeypathInformationSet(payjoinPsbt.data.inputs)
) {
throw new Error(
"Keypath information should not be included in the receiver's PSBT",
);
} }
const sanityResult = checkSanity(payjoinPsbt); const sanityResult = checkSanity(payjoinPsbt);
if(Object.keys(sanityResult).length > 0){ if (Object.keys(sanityResult).length > 0) {
throw new Error(`Receiver's PSBT is insane: ${JSON.stringify(sanityResult)}`); throw new Error(
`Receiver's PSBT is insane: ${JSON.stringify(sanityResult)}`,
);
} }
// We make sure we don't sign what should not be signed // We make sure we don't sign what should not be signed
@ -50,7 +77,7 @@ export async function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall:
const outputLegacy = getGlobalTransaction(payjoinPsbt).outs[index]; const outputLegacy = getGlobalTransaction(payjoinPsbt).outs[index];
// Make sure only our output has any information // Make sure only our output has any information
delete output.bip32Derivation; delete output.bip32Derivation;
psbt.data.outputs.forEach(originalOutput => { psbt.data.outputs.forEach((originalOutput): void => {
// update the payjoin outputs // update the payjoin outputs
if ( if (
outputLegacy.script.equals( outputLegacy.script.equals(
@ -58,9 +85,9 @@ export async function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall:
// Can we assume output will contain redeemScript and witnessScript? // Can we assume output will contain redeemScript and witnessScript?
// If so, we could decompile scriptPubkey, RS, and WS, and search for // If so, we could decompile scriptPubkey, RS, and WS, and search for
// the pubkey and its hash160. // the pubkey and its hash160.
p2wpkh({ payments.p2wpkh({
pubkey: originalOutput.bip32Derivation.pubkey, pubkey: originalOutput.bip32Derivation![0].pubkey,
}).output, }).output!,
) )
) )
payjoinPsbt.updateOutput(index, originalOutput); payjoinPsbt.updateOutput(index, originalOutput);
@ -79,14 +106,23 @@ export async function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall:
// TODO: * check if the difference is due to adjusting fee to increase transaction size // TODO: * check if the difference is due to adjusting fee to increase transaction size
} }
export function requestPayjoin(psbt: Psbt, payjoinEndpoint: string) { export async function requestPayjoin(
return requestPayjoinWithCustomRemoteCall(psbt, psbt1 => doRequest(psbt1, payjoinEndpoint)); psbt: Psbt,
payjoinEndpoint: string,
): Promise<void> {
return requestPayjoinWithCustomRemoteCall(
psbt,
(psbt1): Promise<Nullable<Psbt>> => doRequest(psbt1, payjoinEndpoint),
);
} }
function checkSanity(psbt: Psbt): { [index: number]: string[] } { function checkSanity(psbt: Psbt): { [index: number]: string[] } {
const result: { [index: number]: string[] } = {}; const result: { [index: number]: string[] } = {};
psbt.data.inputs.forEach((value, index) => { psbt.data.inputs.forEach((value, index): void => {
const sanityResult = checkInputSanity(value, getGlobalTransaction(psbt).ins[index]); const sanityResult = checkInputSanity(
value,
getGlobalTransaction(psbt).ins[index],
);
if (sanityResult.length > 0) { if (sanityResult.length > 0) {
result[index] = sanityResult; result[index] = sanityResult;
} }
@ -94,7 +130,7 @@ function checkSanity(psbt: Psbt): { [index: number]: string[] } {
return result; return result;
} }
function checkInputSanity(input: PsbtInput, txInput: Input): string[] { function checkInputSanity(input: PsbtInput, txInput: TxInput): string[] {
const errors: string[] = []; const errors: string[] = [];
if (isFinalized(input)) { if (isFinalized(input)) {
if (input.partialSig && input.partialSig.length > 0) { if (input.partialSig && input.partialSig.length > 0) {
@ -126,36 +162,61 @@ function checkInputSanity(input: PsbtInput, txInput: Input): string[] {
} }
if (input.nonWitnessUtxo) { if (input.nonWitnessUtxo) {
//TODO: get hash // TODO: get hash
const prevOutTxId = input.nonWitnessUtxo; const prevOutTxId = input.nonWitnessUtxo;
let validOutpoint = true; let validOutpoint = true;
if (txInput.hash != prevOutTxId) { if (txInput.hash !== prevOutTxId) {
errors.push('non_witness_utxo does not match the transaction id referenced by the global transaction sign'); errors.push(
'non_witness_utxo does not match the transaction id referenced by the global transaction sign',
);
validOutpoint = false; validOutpoint = false;
} }
// @ts-ignore
if (txInput.index >= input.nonWitnessUtxo.Outputs.length) { if (txInput.index >= input.nonWitnessUtxo.Outputs.length) {
errors.push('Global transaction referencing an out of bound output in non_witness_utxo'); errors.push(
'Global transaction referencing an out of bound output in non_witness_utxo',
);
validOutpoint = false; validOutpoint = false;
} }
if (input.redeemScript && validOutpoint) { if (input.redeemScript && validOutpoint) {
if (input.redeemScript.Hash.ScriptPubKey != input.nonWitnessUtxo.Outputs[txInput.index].ScriptPubKey) if (
errors.push('The redeem_script is not coherent with the scriptPubKey of the non_witness_utxo'); // @ts-ignore
input.redeemScript.Hash.ScriptPubKey !==
// @ts-ignore
input.nonWitnessUtxo.Outputs[txInput.index].ScriptPubKey
)
errors.push(
'The redeem_script is not coherent with the scriptPubKey of the non_witness_utxo',
);
} }
} }
if (input.witnessUtxo) { if (input.witnessUtxo) {
if (input.redeemScript) { if (input.redeemScript) {
if (input.redeemScript.Hash.ScriptPubKey != input.witnessUtxo.ScriptPubKey) if (
errors.push('The redeem_script is not coherent with the scriptPubKey of the witness_utxo'); // @ts-ignore
if (input.witnessScript && input.redeemScript.Hash.ScriptPubKey !== input.witnessUtxo.ScriptPubKey
)
errors.push(
'The redeem_script is not coherent with the scriptPubKey of the witness_utxo',
);
if (
input.witnessScript &&
input.redeemScript && input.redeemScript &&
PayToWitScriptHashTemplate.Instance.ExtractScriptPubKeyParameters(input.redeemScript) != input.witnessScript.WitHash) // @ts-ignore
errors.push('witnessScript with witness UTXO does not match the redeemScript'); PayToWitScriptHashTemplate.Instance.ExtractScriptPubKeyParameters(
input.redeemScript,
// @ts-ignore
) !== input.witnessScript.WitHash
)
errors.push(
'witnessScript with witness UTXO does not match the redeemScript',
);
} }
} }
//figure out how to port this lofic // figure out how to port this lofic
// if (input.witnessUtxo.ScriptPubKey is Script s) // if (input.witnessUtxo.ScriptPubKey is Script s)
// { // {
// //
@ -165,18 +226,24 @@ function checkInputSanity(input: PsbtInput, txInput: Input): string[] {
// errors.push('A Witness UTXO is provided for a non-witness input'); // errors.push('A Witness UTXO is provided for a non-witness input');
// } // }
return errors; return errors;
} }
function hasKeypathInformationSet(
function hasKeypathInformationSet(items: { bip32Derivation?: Bip32Derivation[] }[]): boolean { items: { bip32Derivation?: Bip32Derivation[] }[],
return items.filter(value => value.bip32Derivation && value.bip32Derivation.length > 0).length > 0; ): boolean {
return (
items.filter(
(value): boolean =>
!!value.bip32Derivation && value.bip32Derivation.length > 0,
).length > 0
);
} }
function isFinalized(input: PsbtInput) { function isFinalized(input: PsbtInput): boolean {
return input.finalScriptSig !== undefined || return (
input.finalScriptWitness !== undefined; input.finalScriptSig !== undefined || input.finalScriptWitness !== undefined
);
} }
function getGlobalTransaction(psbt: Psbt): Transaction { function getGlobalTransaction(psbt: Psbt): Transaction {
@ -186,14 +253,17 @@ function getGlobalTransaction(psbt: Psbt): Transaction {
return psbt.__CACHE.__TX; return psbt.__CACHE.__TX;
} }
function doRequest(psbt: Psbt, payjoinEndpoint: string): Promise<Nullable<Psbt>> { function doRequest(
return new Promise<Nullable<Psbt>>((resolve, reject) => { psbt: Psbt,
payjoinEndpoint: string,
): Promise<Nullable<Psbt>> {
return new Promise<Nullable<Psbt>>((resolve, reject): void => {
if (!psbt) { if (!psbt) {
reject(); reject();
} }
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => { xhr.onreadystatechange = (): void => {
if (xhr.readyState !== 4) return; if (xhr.readyState !== 4) return;
if (xhr.status >= 200 && xhr.status < 300) { if (xhr.status >= 200 && xhr.status < 300) {
resolve(Psbt.fromHex(xhr.responseText)); resolve(Psbt.fromHex(xhr.responseText));

39
tsconfig.json

@ -1,31 +1,32 @@
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": false, "target": "es2017",
"alwaysStrict": true,
"declaration": true,
"declarationDir": "./types",
"esModuleInterop": false,
"lib": [
"es2017",
"dom"
],
"module": "commonjs", "module": "commonjs",
"noImplicitAny": true,
"noImplicitThis": true,
"outDir": "./src", "outDir": "./src",
"rootDir": "./ts-src", "declaration": true,
"rootDir": "./ts_src",
"types": [
"node",
"jest"
],
"allowJs": false,
"strict": true, "strict": true,
"strictBindCallApply": true, "noImplicitAny": true,
"strictFunctionTypes": true,
"strictNullChecks": true, "strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true, "strictPropertyInitialization": true,
"target": "es2017", "noImplicitThis": true,
"types": [ "alwaysStrict": true,
"node" "esModuleInterop": false,
] "resolveJsonModule": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true
}, },
"include": [ "include": [
"ts-src/*.ts" "ts_src/**/*.ts",
"ts_src/**/*.json"
], ],
"exclude": [ "exclude": [
"**/*.spec.ts", "**/*.spec.ts",

10
tslint.json

@ -16,12 +16,18 @@
"no-bitwise": false, "no-bitwise": false,
"no-console": false, "no-console": false,
"no-empty": [true, "allow-empty-catch"], "no-empty": [true, "allow-empty-catch"],
"no-implicit-dependencies": true, "no-implicit-dependencies": false,
"no-return-await": true, "no-return-await": true,
"no-var-requires": false, "no-var-requires": false,
"no-unused-expression": false, "no-unused-expression": false,
"object-literal-sort-keys": false, "object-literal-sort-keys": false,
"quotemark": [true, "single"], "quotemark": [true, "single", "avoid-escape"],
"typedef": [
true,
"call-signature",
"arrow-call-signature",
"property-declaration"
],
"variable-name": [ "variable-name": [
true, true,
"ban-keywords", "ban-keywords",

Loading…
Cancel
Save