Kukks
5 years ago
commit
cb18b1ca51
15 changed files with 2360 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||
|
node_modules |
||||
|
.nyc_output |
||||
|
coverage |
||||
|
.idea |
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"singleQuote": true, |
||||
|
"trailingComma": "all" |
||||
|
} |
@ -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 |
@ -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) |
@ -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. |
@ -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. |
File diff suppressed because it is too large
@ -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" |
||||
|
} |
||||
|
} |
@ -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,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()); |
||||
|
}); |
||||
|
} |
@ -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/**/*" |
||||
|
] |
||||
|
} |
@ -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": [] |
||||
|
} |
@ -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…
Reference in new issue