Browse Source

initial commit

master
Kukks 5 years ago
commit
cb18b1ca51
  1. 4
      .gitignore
  2. 0
      .prettierignore
  3. 4
      .prettierrc.json
  4. 19
      .travis.yml
  5. 3
      CONTRIBUTING.md
  6. 21
      LICENSE
  7. 35
      README.md
  8. 1958
      package-lock.json
  9. 56
      package.json
  10. 88
      src/index.js
  11. 0
      test/index.js
  12. 99
      ts-src/index.ts
  13. 34
      tsconfig.json
  14. 34
      tslint.json
  15. 5
      types/index.d.ts

4
.gitignore

@ -0,0 +1,4 @@
node_modules
.nyc_output
coverage
.idea

0
.prettierignore

4
.prettierrc.json

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

19
.travis.yml

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

3
CONTRIBUTING.md

@ -0,0 +1,3 @@
# Refer to Bitcoinjs-lib
[Please refer to bitcoinjs-lib CONTRIBUTING for a guide on how to contribute by clicking here.](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/CONTRIBUTING.md)

21
LICENSE

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2011-2018 bitcoinjs-lib contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

35
README.md

@ -0,0 +1,35 @@
# bip32
[![Build Status](https://travis-ci.org/bitcoinjs/bip32.png?branch=master)](https://travis-ci.org/bitcoinjs/bip32)
[![NPM](https://img.shields.io/npm/v/bip32.svg)](https://www.npmjs.org/package/bip32)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
A [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) compatible library written in TypeScript with transpiled JavaScript committed to git.
## Example
TypeScript
``` typescript
import * as bip32 from 'bip32';
import { BIP32Interface } from 'bip32';
let node: BIP32Interface = bip32.fromBase58('xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi');
let child: BIP32Interface = node.derivePath('m/0/0');
// ...
```
NodeJS
``` javascript
let bip32 = require('bip32')
let node = bip32.fromBase58('xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi')
let child = node.derivePath('m/0/0')
// ...
```
## LICENSE [MIT](LICENSE)
A derivation (and extraction for modularity) of the `HDWallet`/`HDNode` written and tested by [bitcoinjs-lib](https://github.com/bitcoinjs/bitcoinjs-lib) contributors since 2014.

1958
package-lock.json

File diff suppressed because it is too large

56
package.json

@ -0,0 +1,56 @@
{
"name": "payjoin-client-js",
"version": "1.0.0",
"description": "A BTCPay Payjoin compatible client",
"keywords": [
"bitcoinjs",
"bitcoin",
"bip79",
"payjoin",
"btcpayserver"
],
"main": "./src/index.js",
"types": "./types/index.d.ts",
"engines": {
"node": ">=6.0.0"
},
"scripts": {
"build": "tsc -p ./tsconfig.json",
"coverage-report": "npm run build && npm run nobuild:coverage-report",
"coverage": "npm run build && npm run nobuild:coverage",
"format": "npm run prettier -- --write",
"format:ci": "npm run prettier -- --check",
"gitdiff:ci": "npm run build && git diff --exit-code",
"lint": "tslint -p tsconfig.json -c tslint.json",
"nobuild:coverage-report": "nyc report --reporter=lcov",
"nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 npm run nobuild:unit",
"nobuild:unit": "tape test/*.js",
"prettier": "prettier 'ts-src/**/*.ts' --ignore-path ./.prettierignore",
"test": "npm run build && npm run format:ci && npm run lint && npm run nobuild:coverage",
"unit": "npm run build && npm run nobuild:unit"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Kukks/payjoin-client-js.git"
},
"files": [
"src",
"types"
],
"dependencies": {
"@types/node": "^13.13.0",
"bitcoinjs-lib": "^5.1.7"
},
"devDependencies": {
"nyc": "^15.0.0",
"prettier": "1.16.4",
"tape": "^4.13.2",
"tslint": "^6.1.1",
"typescript": "3.8.3"
},
"author": "Andrew Camilleri (Kukks)",
"license": "MIT",
"bugs": {
"url": "https://github.com/Kukks/payjoin-client-js/issues"
}
}

88
src/index.js

@ -0,0 +1,88 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
const payments_1 = require("bitcoinjs-lib/types/payments");
async function requestPayjoinWithCustomRemoteCall(psbt, remoteCall) {
const clonedPsbt = psbt.clone();
clonedPsbt.finalizeAllInputs();
// We make sure we don't send unnecessary information to the receiver
for (let index = 0; index < clonedPsbt.inputCount; index++) {
clonedPsbt.clearFinalizedInput(index);
}
clonedPsbt.data.outputs.forEach(output => {
delete output.bip32Derivation;
});
delete clonedPsbt.data.globalMap.globalXpub;
const payjoinPsbt = await remoteCall(clonedPsbt);
if (!payjoinPsbt)
return null;
// no inputs were added?
if (clonedPsbt.inputCount <= payjoinPsbt.inputCount) {
return null;
}
// We make sure we don't sign things what should not be signed
for (let index = 0; index < payjoinPsbt.inputCount; index++) {
// Is Finalized
if (payjoinPsbt.data.inputs[index].finalScriptSig !== undefined ||
payjoinPsbt.data.inputs[index].finalScriptWitness !== undefined)
payjoinPsbt.clearFinalizedInput(index);
}
for (let index = 0; index < payjoinPsbt.data.outputs.length; index++) {
const output = payjoinPsbt.data.outputs[index];
// TODO: bitcoinjs-lib to expose outputs to Psbt class
// instead of using private (JS has no private) attributes
// @ts-ignore
const outputLegacy = payjoinPsbt.__CACHE.__TX.outs[index];
// Make sure only the only our output have 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.
payments_1.p2wpkh({
pubkey: originalOutput.bip32Derivation.pubkey,
}).output))
payjoinPsbt.updateOutput(index, originalOutput);
});
}
// TODO: check payjoinPsbt.version == psbt.version
// TODO: check payjoinPsbt.locktime == psbt.locktime
// TODO: check payjoinPsbt.inputs where input belongs to us, that it is not finalized
// 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
// 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;
function requestPayjoin(psbt, payjoinEndpoint) {
return requestPayjoinWithCustomRemoteCall(psbt, psbt1 => doRequest(psbt1, payjoinEndpoint));
}
exports.requestPayjoin = requestPayjoin;
function doRequest(psbt, payjoinEndpoint) {
return new Promise((resolve, reject) => {
if (!psbt) {
reject();
}
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4)
return;
if (xhr.status >= 200 && xhr.status < 300) {
resolve(bitcoinjs_lib_1.Psbt.fromHex(xhr.responseText));
}
else {
reject(xhr.responseText);
}
};
xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.open('POST', payjoinEndpoint);
xhr.send(psbt.toHex());
});
}

0
test/index.js

99
ts-src/index.ts

@ -0,0 +1,99 @@
import { Psbt, Transaction } from 'bitcoinjs-lib';
import { p2wpkh } from 'bitcoinjs-lib/types/payments';
type Nullable<T> = T | null;
export async function requestPayjoinWithCustomRemoteCall(psbt: Psbt, remoteCall: (psbt: Psbt) => Promise<Nullable<Psbt>>) {
const clonedPsbt = psbt.clone();
clonedPsbt.finalizeAllInputs();
// We make sure we don't send unnecessary information to the receiver
for (let index = 0; index < clonedPsbt.inputCount; index++) {
clonedPsbt.clearFinalizedInput(index);
}
clonedPsbt.data.outputs.forEach(output => {
delete output.bip32Derivation;
});
delete clonedPsbt.data.globalMap.globalXpub;
const payjoinPsbt = await remoteCall(clonedPsbt);
if (!payjoinPsbt) return null;
// no inputs were added?
if (clonedPsbt.inputCount <= payjoinPsbt.inputCount) {
return null;
}
// We make sure we don't sign things what should not be signed
for (let index = 0; index < payjoinPsbt.inputCount; index++) {
// Is Finalized
if (
payjoinPsbt.data.inputs[index].finalScriptSig !== undefined ||
payjoinPsbt.data.inputs[index].finalScriptWitness !== undefined
)
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 the only our output have 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.
p2wpkh({
pubkey: originalOutput.bip32Derivation.pubkey,
}).output,
)
)
payjoinPsbt.updateOutput(index, originalOutput);
});
}
// TODO: check payjoinPsbt.version == psbt.version
// TODO: check payjoinPsbt.locktime == psbt.locktime
// TODO: check payjoinPsbt.inputs where input belongs to us, that it is not finalized
// 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
// 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
}
export function requestPayjoin(psbt: Psbt, payjoinEndpoint: string) {
return requestPayjoinWithCustomRemoteCall(psbt, psbt1 => doRequest(psbt1, payjoinEndpoint));
}
function getGlobalTransaction(psbt: Psbt): Transaction{
// TODO: bitcoinjs-lib to expose outputs to Psbt class
// instead of using private (JS has no private) attributes
// @ts-ignore
return psbt.__CACHE.__TX;
}
function doRequest(psbt: Psbt, payjoinEndpoint: string): Promise<Nullable<Psbt>> {
return new Promise<Nullable<Psbt>>((resolve, reject) => {
if (!psbt) {
reject();
}
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return;
if (xhr.status >= 200 && xhr.status < 300) {
resolve(Psbt.fromHex(xhr.responseText));
} else {
reject(xhr.responseText);
}
};
xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.open('POST', payjoinEndpoint);
xhr.send(psbt.toHex());
});
}

34
tsconfig.json

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

34
tslint.json

@ -0,0 +1,34 @@
{
"defaultSeverity": "error",
"extends": ["tslint:recommended"],
"rules": {
"arrow-parens": [true, "ban-single-arg-parens"],
"curly": false,
"indent": [
true,
"spaces",
2
],
"interface-name": [false],
"match-default-export-name": true,
"max-classes-per-file": [false],
"member-access": [true, "no-public"],
"no-bitwise": false,
"no-console": false,
"no-empty": [true, "allow-empty-catch"],
"no-implicit-dependencies": true,
"no-return-await": true,
"no-var-requires": false,
"no-unused-expression": false,
"object-literal-sort-keys": false,
"quotemark": [true, "single"],
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore",
"allow-pascal-case"
]
},
"rulesDirectory": []
}

5
types/index.d.ts

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