Commit js, ts, and definitions in separate folders
This commit is contained in:
parent
e7ac2b9a4e
commit
bc28949056
148 changed files with 3850 additions and 39 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
|||
coverage
|
||||
dist
|
||||
node_modules
|
||||
.nyc_output
|
||||
npm-debug.log
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
"name": "bitcoinjs-lib",
|
||||
"version": "4.0.2",
|
||||
"description": "Client-side Bitcoin JavaScript library",
|
||||
"main": "./dist/src/index.js",
|
||||
"types": "./dist/src/index.d.ts",
|
||||
"main": "./src/index.js",
|
||||
"types": "./types/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
|
@ -24,7 +24,7 @@
|
|||
"nobuild:coverage-html": "nyc report --reporter=html",
|
||||
"nobuild:coverage": "nyc --check-coverage --branches 90 --functions 90 --lines 90 mocha",
|
||||
"nobuild:integration": "mocha --timeout 50000 test/integration/",
|
||||
"nobuild:standard": "standard src/**/*.ts",
|
||||
"nobuild:standard": "standard ts_src/**/*.ts",
|
||||
"nobuild:unit": "mocha",
|
||||
"prepare": "npm run build",
|
||||
"standard": "npm run build && npm run nobuild:standard",
|
||||
|
@ -36,7 +36,7 @@
|
|||
"url": "https://github.com/bitcoinjs/bitcoinjs-lib.git"
|
||||
},
|
||||
"files": [
|
||||
"dist/src"
|
||||
"src"
|
||||
],
|
||||
"dependencies": {
|
||||
"@types/node": "^10.12.18",
|
||||
|
|
99
src/address.js
Normal file
99
src/address.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const types = require("./types");
|
||||
const bscript = require("./script");
|
||||
const networks = require("./networks");
|
||||
const payments = require("./payments");
|
||||
const bech32 = require('bech32');
|
||||
const bs58check = require('bs58check');
|
||||
const typeforce = require('typeforce');
|
||||
function fromBase58Check(address) {
|
||||
const payload = bs58check.decode(address);
|
||||
// TODO: 4.0.0, move to "toOutputScript"
|
||||
if (payload.length < 21)
|
||||
throw new TypeError(address + ' is too short');
|
||||
if (payload.length > 21)
|
||||
throw new TypeError(address + ' is too long');
|
||||
const version = payload.readUInt8(0);
|
||||
const hash = payload.slice(1);
|
||||
return { version: version, hash: hash };
|
||||
}
|
||||
exports.fromBase58Check = fromBase58Check;
|
||||
function fromBech32(address) {
|
||||
const result = bech32.decode(address);
|
||||
const data = bech32.fromWords(result.words.slice(1));
|
||||
return {
|
||||
version: result.words[0],
|
||||
prefix: result.prefix,
|
||||
data: Buffer.from(data)
|
||||
};
|
||||
}
|
||||
exports.fromBech32 = fromBech32;
|
||||
function toBase58Check(hash, version) {
|
||||
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
|
||||
const payload = Buffer.allocUnsafe(21);
|
||||
payload.writeUInt8(version, 0);
|
||||
hash.copy(payload, 1);
|
||||
return bs58check.encode(payload);
|
||||
}
|
||||
exports.toBase58Check = toBase58Check;
|
||||
function toBech32(data, version, prefix) {
|
||||
const words = bech32.toWords(data);
|
||||
words.unshift(version);
|
||||
return bech32.encode(prefix, words);
|
||||
}
|
||||
exports.toBech32 = toBech32;
|
||||
function fromOutputScript(output, network) {
|
||||
network = network || networks.bitcoin;
|
||||
try {
|
||||
return payments.p2pkh({ output, network }).address;
|
||||
}
|
||||
catch (e) { }
|
||||
try {
|
||||
return payments.p2sh({ output, network }).address;
|
||||
}
|
||||
catch (e) { }
|
||||
try {
|
||||
return payments.p2wpkh({ output, network }).address;
|
||||
}
|
||||
catch (e) { }
|
||||
try {
|
||||
return payments.p2wsh({ output, network }).address;
|
||||
}
|
||||
catch (e) { }
|
||||
throw new Error(bscript.toASM(output) + ' has no matching Address');
|
||||
}
|
||||
exports.fromOutputScript = fromOutputScript;
|
||||
function toOutputScript(address, network) {
|
||||
network = network || networks.bitcoin;
|
||||
let decodeBase58 = undefined;
|
||||
let decodeBech32 = undefined;
|
||||
try {
|
||||
decodeBase58 = fromBase58Check(address);
|
||||
}
|
||||
catch (e) { }
|
||||
if (decodeBase58) {
|
||||
if (decodeBase58.version === network.pubKeyHash)
|
||||
return payments.p2pkh({ hash: decodeBase58.hash }).output;
|
||||
if (decodeBase58.version === network.scriptHash)
|
||||
return payments.p2sh({ hash: decodeBase58.hash }).output;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
decodeBech32 = fromBech32(address);
|
||||
}
|
||||
catch (e) { }
|
||||
if (decodeBech32) {
|
||||
if (decodeBech32.prefix !== network.bech32)
|
||||
throw new Error(address + ' has an invalid prefix');
|
||||
if (decodeBech32.version === 0) {
|
||||
if (decodeBech32.data.length === 20)
|
||||
return payments.p2wpkh({ hash: decodeBech32.data }).output;
|
||||
if (decodeBech32.data.length === 32)
|
||||
return payments.p2wsh({ hash: decodeBech32.data }).output;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(address + ' has no matching Script');
|
||||
}
|
||||
exports.toOutputScript = toOutputScript;
|
190
src/block.js
Normal file
190
src/block.js
Normal file
|
@ -0,0 +1,190 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const transaction_1 = require("./transaction");
|
||||
const types = require("./types");
|
||||
const bcrypto = require("./crypto");
|
||||
const bufferutils_1 = require("./bufferutils");
|
||||
const fastMerkleRoot = require('merkle-lib/fastRoot');
|
||||
const typeforce = require('typeforce');
|
||||
const varuint = require('varuint-bitcoin');
|
||||
const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions');
|
||||
const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block');
|
||||
const errorBufferTooSmall = new Error('Buffer too small (< 80 bytes)');
|
||||
function txesHaveWitness(transactions) {
|
||||
return transactions !== undefined &&
|
||||
transactions instanceof Array &&
|
||||
transactions[0] &&
|
||||
transactions[0].ins &&
|
||||
transactions[0].ins instanceof Array &&
|
||||
transactions[0].ins[0] &&
|
||||
transactions[0].ins[0].witness &&
|
||||
transactions[0].ins[0].witness instanceof Array &&
|
||||
transactions[0].ins[0].witness.length > 0;
|
||||
}
|
||||
class Block {
|
||||
constructor() {
|
||||
this.version = 1;
|
||||
this.timestamp = 0;
|
||||
this.bits = 0;
|
||||
this.nonce = 0;
|
||||
this.prevHash = undefined;
|
||||
this.merkleRoot = undefined;
|
||||
this.witnessCommit = undefined;
|
||||
this.transactions = undefined;
|
||||
}
|
||||
static fromBuffer(buffer) {
|
||||
if (buffer.length < 80)
|
||||
throw errorBufferTooSmall;
|
||||
let offset = 0;
|
||||
const readSlice = (n) => {
|
||||
offset += n;
|
||||
return buffer.slice(offset - n, offset);
|
||||
};
|
||||
const readUInt32 = () => {
|
||||
const i = buffer.readUInt32LE(offset);
|
||||
offset += 4;
|
||||
return i;
|
||||
};
|
||||
const readInt32 = () => {
|
||||
const i = buffer.readInt32LE(offset);
|
||||
offset += 4;
|
||||
return i;
|
||||
};
|
||||
const block = new Block();
|
||||
block.version = readInt32();
|
||||
block.prevHash = readSlice(32);
|
||||
block.merkleRoot = readSlice(32);
|
||||
block.timestamp = readUInt32();
|
||||
block.bits = readUInt32();
|
||||
block.nonce = readUInt32();
|
||||
if (buffer.length === 80)
|
||||
return block;
|
||||
const readVarInt = () => {
|
||||
const vi = varuint.decode(buffer, offset);
|
||||
offset += varuint.decode.bytes;
|
||||
return vi;
|
||||
};
|
||||
const readTransaction = () => {
|
||||
const tx = transaction_1.Transaction.fromBuffer(buffer.slice(offset), true);
|
||||
offset += tx.byteLength();
|
||||
return tx;
|
||||
};
|
||||
const nTransactions = readVarInt();
|
||||
block.transactions = [];
|
||||
for (var i = 0; i < nTransactions; ++i) {
|
||||
const tx = readTransaction();
|
||||
block.transactions.push(tx);
|
||||
}
|
||||
// This Block contains a witness commit
|
||||
if (block.hasWitnessCommit()) {
|
||||
// The merkle root for the witness data is in an OP_RETURN output.
|
||||
// There is no rule for the index of the output, so use filter to find it.
|
||||
// The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed
|
||||
// If multiple commits are found, the output with highest index is assumed.
|
||||
let witnessCommits = block.transactions[0].outs
|
||||
.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')))
|
||||
.map(out => out.script.slice(6, 38));
|
||||
// Use the commit with the highest output (should only be one though)
|
||||
block.witnessCommit = witnessCommits[witnessCommits.length - 1];
|
||||
}
|
||||
return block;
|
||||
}
|
||||
static fromHex(hex) {
|
||||
return Block.fromBuffer(Buffer.from(hex, 'hex'));
|
||||
}
|
||||
static calculateTarget(bits) {
|
||||
const exponent = ((bits & 0xff000000) >> 24) - 3;
|
||||
const mantissa = bits & 0x007fffff;
|
||||
const target = Buffer.alloc(32, 0);
|
||||
target.writeUIntBE(mantissa, 29 - exponent, 3);
|
||||
return target;
|
||||
}
|
||||
static calculateMerkleRoot(transactions, forWitness) {
|
||||
typeforce([{ getHash: types.Function }], transactions);
|
||||
if (transactions.length === 0)
|
||||
throw errorMerkleNoTxes;
|
||||
if (forWitness && !txesHaveWitness(transactions))
|
||||
throw errorWitnessNotSegwit;
|
||||
const hashes = transactions.map(transaction => transaction.getHash(forWitness));
|
||||
const rootHash = fastMerkleRoot(hashes, bcrypto.hash256);
|
||||
return forWitness
|
||||
? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]))
|
||||
: rootHash;
|
||||
}
|
||||
hasWitnessCommit() {
|
||||
return txesHaveWitness(this.transactions);
|
||||
}
|
||||
byteLength(headersOnly) {
|
||||
if (headersOnly || !this.transactions)
|
||||
return 80;
|
||||
return 80 + varuint.encodingLength(this.transactions.length) +
|
||||
this.transactions.reduce((a, x) => a + x.byteLength(), 0);
|
||||
}
|
||||
getHash() {
|
||||
return bcrypto.hash256(this.toBuffer(true));
|
||||
}
|
||||
getId() {
|
||||
return bufferutils_1.reverseBuffer(this.getHash()).toString('hex');
|
||||
}
|
||||
getUTCDate() {
|
||||
const date = new Date(0); // epoch
|
||||
date.setUTCSeconds(this.timestamp);
|
||||
return date;
|
||||
}
|
||||
// TODO: buffer, offset compatibility
|
||||
toBuffer(headersOnly) {
|
||||
const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly));
|
||||
let offset = 0;
|
||||
const writeSlice = (slice) => {
|
||||
slice.copy(buffer, offset);
|
||||
offset += slice.length;
|
||||
};
|
||||
const writeInt32 = (i) => {
|
||||
buffer.writeInt32LE(i, offset);
|
||||
offset += 4;
|
||||
};
|
||||
const writeUInt32 = (i) => {
|
||||
buffer.writeUInt32LE(i, offset);
|
||||
offset += 4;
|
||||
};
|
||||
writeInt32(this.version);
|
||||
writeSlice(this.prevHash);
|
||||
writeSlice(this.merkleRoot);
|
||||
writeUInt32(this.timestamp);
|
||||
writeUInt32(this.bits);
|
||||
writeUInt32(this.nonce);
|
||||
if (headersOnly || !this.transactions)
|
||||
return buffer;
|
||||
varuint.encode(this.transactions.length, buffer, offset);
|
||||
offset += varuint.encode.bytes;
|
||||
this.transactions.forEach(tx => {
|
||||
const txSize = tx.byteLength(); // TODO: extract from toBuffer?
|
||||
tx.toBuffer(buffer, offset);
|
||||
offset += txSize;
|
||||
});
|
||||
return buffer;
|
||||
}
|
||||
toHex(headersOnly) {
|
||||
return this.toBuffer(headersOnly).toString('hex');
|
||||
}
|
||||
checkMerkleRoot() {
|
||||
if (!this.transactions)
|
||||
throw errorMerkleNoTxes;
|
||||
const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions);
|
||||
return this.merkleRoot.compare(actualMerkleRoot) === 0;
|
||||
}
|
||||
checkWitnessCommit() {
|
||||
if (!this.transactions)
|
||||
throw errorMerkleNoTxes;
|
||||
if (!this.hasWitnessCommit())
|
||||
throw errorWitnessNotSegwit;
|
||||
const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true);
|
||||
return this.witnessCommit.compare(actualWitnessCommit) === 0;
|
||||
}
|
||||
checkProofOfWork() {
|
||||
const hash = bufferutils_1.reverseBuffer(this.getHash());
|
||||
const target = Block.calculateTarget(this.bits);
|
||||
return hash.compare(target) <= 0;
|
||||
}
|
||||
}
|
||||
exports.Block = Block;
|
42
src/bufferutils.js
Normal file
42
src/bufferutils.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// https://github.com/feross/buffer/blob/master/index.js#L1127
|
||||
function verifuint(value, max) {
|
||||
if (typeof value !== 'number')
|
||||
throw new Error('cannot write a non-number as a number');
|
||||
if (value < 0)
|
||||
throw new Error('specified a negative value for writing an unsigned value');
|
||||
if (value > max)
|
||||
throw new Error('RangeError: value out of range');
|
||||
if (Math.floor(value) !== value)
|
||||
throw new Error('value has a fractional component');
|
||||
}
|
||||
function readUInt64LE(buffer, offset) {
|
||||
const a = buffer.readUInt32LE(offset);
|
||||
let b = buffer.readUInt32LE(offset + 4);
|
||||
b *= 0x100000000;
|
||||
verifuint(b + a, 0x001fffffffffffff);
|
||||
return b + a;
|
||||
}
|
||||
exports.readUInt64LE = readUInt64LE;
|
||||
function writeUInt64LE(buffer, value, offset) {
|
||||
verifuint(value, 0x001fffffffffffff);
|
||||
buffer.writeInt32LE(value & -1, offset);
|
||||
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4);
|
||||
return offset + 8;
|
||||
}
|
||||
exports.writeUInt64LE = writeUInt64LE;
|
||||
function reverseBuffer(buffer) {
|
||||
if (buffer.length < 1)
|
||||
return buffer;
|
||||
let j = buffer.length - 1;
|
||||
let tmp = 0;
|
||||
for (let i = 0; i < buffer.length / 2; i++) {
|
||||
tmp = buffer[i];
|
||||
buffer[i] = buffer[j];
|
||||
buffer[j] = tmp;
|
||||
j--;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
exports.reverseBuffer = reverseBuffer;
|
75
src/classify.js
Normal file
75
src/classify.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const script_1 = require("./script");
|
||||
const multisig = require("./templates/multisig");
|
||||
const nullData = require("./templates/nulldata");
|
||||
const pubKey = require("./templates/pubkey");
|
||||
const pubKeyHash = require("./templates/pubkeyhash");
|
||||
const scriptHash = require("./templates/scripthash");
|
||||
const witnessPubKeyHash = require("./templates/witnesspubkeyhash");
|
||||
const witnessScriptHash = require("./templates/witnessscripthash");
|
||||
const witnessCommitment = require("./templates/witnesscommitment");
|
||||
const types = {
|
||||
P2MS: 'multisig',
|
||||
NONSTANDARD: 'nonstandard',
|
||||
NULLDATA: 'nulldata',
|
||||
P2PK: 'pubkey',
|
||||
P2PKH: 'pubkeyhash',
|
||||
P2SH: 'scripthash',
|
||||
P2WPKH: 'witnesspubkeyhash',
|
||||
P2WSH: 'witnessscripthash',
|
||||
WITNESS_COMMITMENT: 'witnesscommitment'
|
||||
};
|
||||
exports.types = types;
|
||||
function classifyOutput(script) {
|
||||
if (witnessPubKeyHash.output.check(script))
|
||||
return types.P2WPKH;
|
||||
if (witnessScriptHash.output.check(script))
|
||||
return types.P2WSH;
|
||||
if (pubKeyHash.output.check(script))
|
||||
return types.P2PKH;
|
||||
if (scriptHash.output.check(script))
|
||||
return types.P2SH;
|
||||
// XXX: optimization, below functions .decompile before use
|
||||
const chunks = script_1.decompile(script);
|
||||
if (!chunks)
|
||||
throw new TypeError('Invalid script');
|
||||
if (multisig.output.check(chunks))
|
||||
return types.P2MS;
|
||||
if (pubKey.output.check(chunks))
|
||||
return types.P2PK;
|
||||
if (witnessCommitment.output.check(chunks))
|
||||
return types.WITNESS_COMMITMENT;
|
||||
if (nullData.output.check(chunks))
|
||||
return types.NULLDATA;
|
||||
return types.NONSTANDARD;
|
||||
}
|
||||
exports.output = classifyOutput;
|
||||
function classifyInput(script, allowIncomplete) {
|
||||
// XXX: optimization, below functions .decompile before use
|
||||
const chunks = script_1.decompile(script);
|
||||
if (!chunks)
|
||||
throw new TypeError('Invalid script');
|
||||
if (pubKeyHash.input.check(chunks))
|
||||
return types.P2PKH;
|
||||
if (scriptHash.input.check(chunks, allowIncomplete))
|
||||
return types.P2SH;
|
||||
if (multisig.input.check(chunks, allowIncomplete))
|
||||
return types.P2MS;
|
||||
if (pubKey.input.check(chunks))
|
||||
return types.P2PK;
|
||||
return types.NONSTANDARD;
|
||||
}
|
||||
exports.input = classifyInput;
|
||||
function classifyWitness(script, allowIncomplete) {
|
||||
// XXX: optimization, below functions .decompile before use
|
||||
const chunks = script_1.decompile(script);
|
||||
if (!chunks)
|
||||
throw new TypeError('Invalid script');
|
||||
if (witnessPubKeyHash.input.check(chunks))
|
||||
return types.P2WPKH;
|
||||
if (witnessScriptHash.input.check(chunks, allowIncomplete))
|
||||
return types.P2WSH;
|
||||
return types.NONSTANDARD;
|
||||
}
|
||||
exports.witness = classifyWitness;
|
23
src/crypto.js
Normal file
23
src/crypto.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const createHash = require('create-hash');
|
||||
function ripemd160(buffer) {
|
||||
return createHash('rmd160').update(buffer).digest();
|
||||
}
|
||||
exports.ripemd160 = ripemd160;
|
||||
function sha1(buffer) {
|
||||
return createHash('sha1').update(buffer).digest();
|
||||
}
|
||||
exports.sha1 = sha1;
|
||||
function sha256(buffer) {
|
||||
return createHash('sha256').update(buffer).digest();
|
||||
}
|
||||
exports.sha256 = sha256;
|
||||
function hash160(buffer) {
|
||||
return ripemd160(sha256(buffer));
|
||||
}
|
||||
exports.hash160 = hash160;
|
||||
function hash256(buffer) {
|
||||
return sha256(sha256(buffer));
|
||||
}
|
||||
exports.hash256 = hash256;
|
97
src/ecpair.js
Normal file
97
src/ecpair.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const NETWORKS = require("./networks");
|
||||
const types = require("./types");
|
||||
const ecc = require('tiny-secp256k1');
|
||||
const randomBytes = require('randombytes');
|
||||
const typeforce = require('typeforce');
|
||||
const wif = require('wif');
|
||||
const isOptions = typeforce.maybe(typeforce.compile({
|
||||
compressed: types.maybe(types.Boolean),
|
||||
network: types.maybe(types.Network)
|
||||
}));
|
||||
class ECPair {
|
||||
constructor(d, Q, options) {
|
||||
if (options === undefined)
|
||||
options = {};
|
||||
this.compressed = options.compressed === undefined ? true : options.compressed;
|
||||
this.network = options.network || NETWORKS.bitcoin;
|
||||
this.__d = undefined;
|
||||
this.__Q = undefined;
|
||||
if (d !== undefined)
|
||||
this.__d = d;
|
||||
if (Q !== undefined)
|
||||
this.__Q = ecc.pointCompress(Q, this.compressed);
|
||||
}
|
||||
get privateKey() {
|
||||
return this.__d;
|
||||
}
|
||||
get publicKey() {
|
||||
if (!this.__Q)
|
||||
this.__Q = ecc.pointFromScalar(this.__d, this.compressed);
|
||||
return this.__Q;
|
||||
}
|
||||
toWIF() {
|
||||
if (!this.__d)
|
||||
throw new Error('Missing private key');
|
||||
return wif.encode(this.network.wif, this.__d, this.compressed);
|
||||
}
|
||||
sign(hash) {
|
||||
if (!this.__d)
|
||||
throw new Error('Missing private key');
|
||||
return ecc.sign(hash, this.__d);
|
||||
}
|
||||
verify(hash, signature) {
|
||||
return ecc.verify(hash, this.publicKey, signature);
|
||||
}
|
||||
}
|
||||
function fromPrivateKey(buffer, options) {
|
||||
typeforce(types.Buffer256bit, buffer);
|
||||
if (!ecc.isPrivate(buffer))
|
||||
throw new TypeError('Private key not in range [1, n)');
|
||||
typeforce(isOptions, options);
|
||||
return new ECPair(buffer, undefined, options);
|
||||
}
|
||||
exports.fromPrivateKey = fromPrivateKey;
|
||||
function fromPublicKey(buffer, options) {
|
||||
typeforce(ecc.isPoint, buffer);
|
||||
typeforce(isOptions, options);
|
||||
return new ECPair(undefined, buffer, options);
|
||||
}
|
||||
exports.fromPublicKey = fromPublicKey;
|
||||
function fromWIF(string, network) {
|
||||
const decoded = wif.decode(string);
|
||||
const version = decoded.version;
|
||||
// list of networks?
|
||||
if (types.Array(network)) {
|
||||
network = network.filter(function (x) {
|
||||
return version === x.wif;
|
||||
}).pop();
|
||||
if (!network)
|
||||
throw new Error('Unknown network version');
|
||||
// otherwise, assume a network object (or default to bitcoin)
|
||||
}
|
||||
else {
|
||||
network = network || NETWORKS.bitcoin;
|
||||
if (version !== network.wif)
|
||||
throw new Error('Invalid network version');
|
||||
}
|
||||
return fromPrivateKey(decoded.privateKey, {
|
||||
compressed: decoded.compressed,
|
||||
network: network
|
||||
});
|
||||
}
|
||||
exports.fromWIF = fromWIF;
|
||||
function makeRandom(options) {
|
||||
typeforce(isOptions, options);
|
||||
if (options === undefined)
|
||||
options = {};
|
||||
const rng = options.rng || randomBytes;
|
||||
let d;
|
||||
do {
|
||||
d = rng(32);
|
||||
typeforce(types.Buffer256bit, d);
|
||||
} while (!ecc.isPrivate(d));
|
||||
return fromPrivateKey(d, options);
|
||||
}
|
||||
exports.makeRandom = makeRandom;
|
24
src/index.js
Normal file
24
src/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bip32 = require("bip32");
|
||||
exports.bip32 = bip32;
|
||||
const ECPair = require("./ecpair");
|
||||
exports.ECPair = ECPair;
|
||||
const address = require("./address");
|
||||
exports.address = address;
|
||||
const crypto = require("./crypto");
|
||||
exports.crypto = crypto;
|
||||
const networks = require("./networks");
|
||||
exports.networks = networks;
|
||||
const payments = require("./payments");
|
||||
exports.payments = payments;
|
||||
const script = require("./script");
|
||||
exports.script = script;
|
||||
var block_1 = require("./block");
|
||||
exports.Block = block_1.Block;
|
||||
var transaction_1 = require("./transaction");
|
||||
exports.Transaction = transaction_1.Transaction;
|
||||
var transaction_builder_1 = require("./transaction_builder");
|
||||
exports.TransactionBuilder = transaction_builder_1.TransactionBuilder;
|
||||
var script_1 = require("./script");
|
||||
exports.opcodes = script_1.OPS;
|
35
src/networks.js
Normal file
35
src/networks.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.bitcoin = {
|
||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||
bech32: 'bc',
|
||||
bip32: {
|
||||
public: 0x0488b21e,
|
||||
private: 0x0488ade4
|
||||
},
|
||||
pubKeyHash: 0x00,
|
||||
scriptHash: 0x05,
|
||||
wif: 0x80
|
||||
};
|
||||
exports.regtest = {
|
||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||
bech32: 'bcrt',
|
||||
bip32: {
|
||||
public: 0x043587cf,
|
||||
private: 0x04358394
|
||||
},
|
||||
pubKeyHash: 0x6f,
|
||||
scriptHash: 0xc4,
|
||||
wif: 0xef
|
||||
};
|
||||
exports.testnet = {
|
||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||
bech32: 'tb',
|
||||
bip32: {
|
||||
public: 0x043587cf,
|
||||
private: 0x04358394
|
||||
},
|
||||
pubKeyHash: 0x6f,
|
||||
scriptHash: 0xc4,
|
||||
wif: 0xef
|
||||
};
|
52
src/payments/embed.js
Normal file
52
src/payments/embed.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../script");
|
||||
const lazy = require("./lazy");
|
||||
const networks_1 = require("../networks");
|
||||
const typef = require('typeforce');
|
||||
const OPS = bscript.OPS;
|
||||
function stacksEqual(a, b) {
|
||||
if (a.length !== b.length)
|
||||
return false;
|
||||
return a.every(function (x, i) {
|
||||
return x.equals(b[i]);
|
||||
});
|
||||
}
|
||||
// output: OP_RETURN ...
|
||||
function p2data(a, opts) {
|
||||
if (!a.data &&
|
||||
!a.output)
|
||||
throw new TypeError('Not enough data');
|
||||
opts = Object.assign({ validate: true }, opts || {});
|
||||
typef({
|
||||
network: typef.maybe(typef.Object),
|
||||
output: typef.maybe(typef.Buffer),
|
||||
data: typef.maybe(typef.arrayOf(typef.Buffer))
|
||||
}, a);
|
||||
const network = a.network || networks_1.bitcoin;
|
||||
const o = { network };
|
||||
lazy.prop(o, 'output', function () {
|
||||
if (!a.data)
|
||||
return;
|
||||
return bscript.compile([OPS.OP_RETURN].concat(a.data));
|
||||
});
|
||||
lazy.prop(o, 'data', function () {
|
||||
if (!a.output)
|
||||
return;
|
||||
return bscript.decompile(a.output).slice(1);
|
||||
});
|
||||
// extended validation
|
||||
if (opts.validate) {
|
||||
if (a.output) {
|
||||
const chunks = bscript.decompile(a.output);
|
||||
if (chunks[0] !== OPS.OP_RETURN)
|
||||
throw new TypeError('Output is invalid');
|
||||
if (!chunks.slice(1).every(typef.Buffer))
|
||||
throw new TypeError('Output is invalid');
|
||||
if (a.data && !stacksEqual(a.data, o.data))
|
||||
throw new TypeError('Data mismatch');
|
||||
}
|
||||
}
|
||||
return Object.assign(o, a);
|
||||
}
|
||||
exports.p2data = p2data;
|
18
src/payments/index.js
Normal file
18
src/payments/index.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const embed_1 = require("./embed");
|
||||
exports.embed = embed_1.p2data;
|
||||
const p2ms_1 = require("./p2ms");
|
||||
exports.p2ms = p2ms_1.p2ms;
|
||||
const p2pk_1 = require("./p2pk");
|
||||
exports.p2pk = p2pk_1.p2pk;
|
||||
const p2pkh_1 = require("./p2pkh");
|
||||
exports.p2pkh = p2pkh_1.p2pkh;
|
||||
const p2sh_1 = require("./p2sh");
|
||||
exports.p2sh = p2sh_1.p2sh;
|
||||
const p2wpkh_1 = require("./p2wpkh");
|
||||
exports.p2wpkh = p2wpkh_1.p2wpkh;
|
||||
const p2wsh_1 = require("./p2wsh");
|
||||
exports.p2wsh = p2wsh_1.p2wsh;
|
||||
// TODO
|
||||
// witness commitment
|
32
src/payments/lazy.js
Normal file
32
src/payments/lazy.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function prop(object, name, f) {
|
||||
Object.defineProperty(object, name, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
let value = f.call(this);
|
||||
this[name] = value;
|
||||
return value;
|
||||
},
|
||||
set: function (value) {
|
||||
Object.defineProperty(this, name, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
value: value,
|
||||
writable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.prop = prop;
|
||||
function value(f) {
|
||||
let value;
|
||||
return function () {
|
||||
if (value !== undefined)
|
||||
return value;
|
||||
value = f();
|
||||
return value;
|
||||
};
|
||||
}
|
||||
exports.value = value;
|
144
src/payments/p2ms.js
Normal file
144
src/payments/p2ms.js
Normal file
|
@ -0,0 +1,144 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../script");
|
||||
const lazy = require("./lazy");
|
||||
const networks_1 = require("../networks");
|
||||
const OPS = bscript.OPS;
|
||||
const typef = require('typeforce');
|
||||
const ecc = require('tiny-secp256k1');
|
||||
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
|
||||
function stacksEqual(a, b) {
|
||||
if (a.length !== b.length)
|
||||
return false;
|
||||
return a.every(function (x, i) {
|
||||
return x.equals(b[i]);
|
||||
});
|
||||
}
|
||||
// input: OP_0 [signatures ...]
|
||||
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
||||
function p2ms(a, opts) {
|
||||
if (!a.input &&
|
||||
!a.output &&
|
||||
!(a.pubkeys && a.m !== undefined) &&
|
||||
!a.signatures)
|
||||
throw new TypeError('Not enough data');
|
||||
opts = Object.assign({ validate: true }, opts || {});
|
||||
function isAcceptableSignature(x) {
|
||||
return bscript.isCanonicalScriptSignature(x) ||
|
||||
(opts.allowIncomplete &&
|
||||
(x === OPS.OP_0)) !== undefined; // eslint-disable-line
|
||||
}
|
||||
typef({
|
||||
network: typef.maybe(typef.Object),
|
||||
m: typef.maybe(typef.Number),
|
||||
n: typef.maybe(typef.Number),
|
||||
output: typef.maybe(typef.Buffer),
|
||||
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
|
||||
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
|
||||
input: typef.maybe(typef.Buffer)
|
||||
}, a);
|
||||
const network = a.network || networks_1.bitcoin;
|
||||
const o = { network };
|
||||
let chunks = [];
|
||||
let decoded = false;
|
||||
function decode(output) {
|
||||
if (decoded)
|
||||
return;
|
||||
decoded = true;
|
||||
chunks = bscript.decompile(output);
|
||||
o.m = chunks[0] - OP_INT_BASE; // eslint-disable-line
|
||||
o.n = chunks[chunks.length - 2] - OP_INT_BASE; // eslint-disable-line
|
||||
o.pubkeys = chunks.slice(1, -2);
|
||||
}
|
||||
lazy.prop(o, 'output', function () {
|
||||
if (!a.m)
|
||||
return;
|
||||
if (!o.n)
|
||||
return;
|
||||
if (!a.pubkeys)
|
||||
return;
|
||||
return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG));
|
||||
});
|
||||
lazy.prop(o, 'm', function () {
|
||||
if (!o.output)
|
||||
return;
|
||||
decode(o.output);
|
||||
return o.m;
|
||||
});
|
||||
lazy.prop(o, 'n', function () {
|
||||
if (!o.pubkeys)
|
||||
return;
|
||||
return o.pubkeys.length;
|
||||
});
|
||||
lazy.prop(o, 'pubkeys', function () {
|
||||
if (!a.output)
|
||||
return;
|
||||
decode(a.output);
|
||||
return o.pubkeys;
|
||||
});
|
||||
lazy.prop(o, 'signatures', function () {
|
||||
if (!a.input)
|
||||
return;
|
||||
return bscript.decompile(a.input).slice(1);
|
||||
});
|
||||
lazy.prop(o, 'input', function () {
|
||||
if (!a.signatures)
|
||||
return;
|
||||
return bscript.compile([OPS.OP_0].concat(a.signatures));
|
||||
});
|
||||
lazy.prop(o, 'witness', function () {
|
||||
if (!o.input)
|
||||
return;
|
||||
return [];
|
||||
});
|
||||
// extended validation
|
||||
if (opts.validate) {
|
||||
if (a.output) {
|
||||
decode(a.output);
|
||||
if (!typef.Number(chunks[0]))
|
||||
throw new TypeError('Output is invalid');
|
||||
if (!typef.Number(chunks[chunks.length - 2]))
|
||||
throw new TypeError('Output is invalid');
|
||||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG)
|
||||
throw new TypeError('Output is invalid');
|
||||
if (o.m <= 0 || // eslint-disable-line
|
||||
o.n > 16 || // eslint-disable-line
|
||||
o.m > o.n || // eslint-disable-line
|
||||
o.n !== chunks.length - 3)
|
||||
throw new TypeError('Output is invalid');
|
||||
if (!o.pubkeys.every(x => ecc.isPoint(x)))
|
||||
throw new TypeError('Output is invalid');
|
||||
if (a.m !== undefined && a.m !== o.m)
|
||||
throw new TypeError('m mismatch');
|
||||
if (a.n !== undefined && a.n !== o.n)
|
||||
throw new TypeError('n mismatch');
|
||||
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys))
|
||||
throw new TypeError('Pubkeys mismatch');
|
||||
}
|
||||
if (a.pubkeys) {
|
||||
if (a.n !== undefined && a.n !== a.pubkeys.length)
|
||||
throw new TypeError('Pubkey count mismatch');
|
||||
o.n = a.pubkeys.length;
|
||||
if (o.n < o.m)
|
||||
throw new TypeError('Pubkey count cannot be less than m');
|
||||
}
|
||||
if (a.signatures) {
|
||||
if (a.signatures.length < o.m)
|
||||
throw new TypeError('Not enough signatures provided');
|
||||
if (a.signatures.length > o.m)
|
||||
throw new TypeError('Too many signatures provided');
|
||||
}
|
||||
if (a.input) {
|
||||
if (a.input[0] !== OPS.OP_0)
|
||||
throw new TypeError('Input is invalid');
|
||||
if (o.signatures.length === 0 || !o.signatures.every(isAcceptableSignature))
|
||||
throw new TypeError('Input has invalid signature(s)');
|
||||
if (a.signatures && !stacksEqual(a.signatures, o.signatures))
|
||||
throw new TypeError('Signature mismatch');
|
||||
if (a.m !== undefined && a.m !== a.signatures.length)
|
||||
throw new TypeError('Signature count mismatch');
|
||||
}
|
||||
}
|
||||
return Object.assign(o, a);
|
||||
}
|
||||
exports.p2ms = p2ms;
|
80
src/payments/p2pk.js
Normal file
80
src/payments/p2pk.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../script");
|
||||
const lazy = require("./lazy");
|
||||
const networks_1 = require("../networks");
|
||||
const typef = require('typeforce');
|
||||
const OPS = bscript.OPS;
|
||||
const ecc = require('tiny-secp256k1');
|
||||
// input: {signature}
|
||||
// output: {pubKey} OP_CHECKSIG
|
||||
function p2pk(a, opts) {
|
||||
if (!a.input &&
|
||||
!a.output &&
|
||||
!a.pubkey &&
|
||||
!a.input &&
|
||||
!a.signature)
|
||||
throw new TypeError('Not enough data');
|
||||
opts = Object.assign({ validate: true }, opts || {});
|
||||
typef({
|
||||
network: typef.maybe(typef.Object),
|
||||
output: typef.maybe(typef.Buffer),
|
||||
pubkey: typef.maybe(ecc.isPoint),
|
||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||
input: typef.maybe(typef.Buffer)
|
||||
}, a);
|
||||
const _chunks = lazy.value(function () { return bscript.decompile(a.input); });
|
||||
const network = a.network || networks_1.bitcoin;
|
||||
const o = { network };
|
||||
lazy.prop(o, 'output', function () {
|
||||
if (!a.pubkey)
|
||||
return;
|
||||
return bscript.compile([
|
||||
a.pubkey,
|
||||
OPS.OP_CHECKSIG
|
||||
]);
|
||||
});
|
||||
lazy.prop(o, 'pubkey', function () {
|
||||
if (!a.output)
|
||||
return;
|
||||
return a.output.slice(1, -1);
|
||||
});
|
||||
lazy.prop(o, 'signature', function () {
|
||||
if (!a.input)
|
||||
return;
|
||||
return _chunks()[0];
|
||||
});
|
||||
lazy.prop(o, 'input', function () {
|
||||
if (!a.signature)
|
||||
return;
|
||||
return bscript.compile([a.signature]);
|
||||
});
|
||||
lazy.prop(o, 'witness', function () {
|
||||
if (!o.input)
|
||||
return;
|
||||
return [];
|
||||
});
|
||||
// extended validation
|
||||
if (opts.validate) {
|
||||
if (a.output) {
|
||||
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG)
|
||||
throw new TypeError('Output is invalid');
|
||||
if (!ecc.isPoint(o.pubkey))
|
||||
throw new TypeError('Output pubkey is invalid');
|
||||
if (a.pubkey && !a.pubkey.equals(o.pubkey))
|
||||
throw new TypeError('Pubkey mismatch');
|
||||
}
|
||||
if (a.signature) {
|
||||
if (a.input && !a.input.equals(o.input))
|
||||
throw new TypeError('Signature mismatch');
|
||||
}
|
||||
if (a.input) {
|
||||
if (_chunks().length !== 1)
|
||||
throw new TypeError('Input is invalid');
|
||||
if (!bscript.isCanonicalScriptSignature(o.signature))
|
||||
throw new TypeError('Input has invalid signature');
|
||||
}
|
||||
}
|
||||
return Object.assign(o, a);
|
||||
}
|
||||
exports.p2pk = p2pk;
|
144
src/payments/p2pkh.js
Normal file
144
src/payments/p2pkh.js
Normal file
|
@ -0,0 +1,144 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../script");
|
||||
const bcrypto = require("../crypto");
|
||||
const lazy = require("./lazy");
|
||||
const networks_1 = require("../networks");
|
||||
const typef = require('typeforce');
|
||||
const OPS = bscript.OPS;
|
||||
const ecc = require('tiny-secp256k1');
|
||||
const bs58check = require('bs58check');
|
||||
// input: {signature} {pubkey}
|
||||
// output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
|
||||
function p2pkh(a, opts) {
|
||||
if (!a.address &&
|
||||
!a.hash &&
|
||||
!a.output &&
|
||||
!a.pubkey &&
|
||||
!a.input)
|
||||
throw new TypeError('Not enough data');
|
||||
opts = Object.assign({ validate: true }, opts || {});
|
||||
typef({
|
||||
network: typef.maybe(typef.Object),
|
||||
address: typef.maybe(typef.String),
|
||||
hash: typef.maybe(typef.BufferN(20)),
|
||||
output: typef.maybe(typef.BufferN(25)),
|
||||
pubkey: typef.maybe(ecc.isPoint),
|
||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||
input: typef.maybe(typef.Buffer)
|
||||
}, a);
|
||||
const _address = lazy.value(function () {
|
||||
const payload = bs58check.decode(a.address);
|
||||
const version = payload.readUInt8(0);
|
||||
const hash = payload.slice(1);
|
||||
return { version, hash };
|
||||
});
|
||||
const _chunks = lazy.value(function () { return bscript.decompile(a.input); });
|
||||
const network = a.network || networks_1.bitcoin;
|
||||
const o = { network };
|
||||
lazy.prop(o, 'address', function () {
|
||||
if (!o.hash)
|
||||
return;
|
||||
const payload = Buffer.allocUnsafe(21);
|
||||
payload.writeUInt8(network.pubKeyHash, 0);
|
||||
o.hash.copy(payload, 1);
|
||||
return bs58check.encode(payload);
|
||||
});
|
||||
lazy.prop(o, 'hash', function () {
|
||||
if (a.output)
|
||||
return a.output.slice(3, 23);
|
||||
if (a.address)
|
||||
return _address().hash;
|
||||
if (a.pubkey || o.pubkey)
|
||||
return bcrypto.hash160(a.pubkey || o.pubkey); // eslint-disable-line
|
||||
});
|
||||
lazy.prop(o, 'output', function () {
|
||||
if (!o.hash)
|
||||
return;
|
||||
return bscript.compile([
|
||||
OPS.OP_DUP,
|
||||
OPS.OP_HASH160,
|
||||
o.hash,
|
||||
OPS.OP_EQUALVERIFY,
|
||||
OPS.OP_CHECKSIG
|
||||
]);
|
||||
});
|
||||
lazy.prop(o, 'pubkey', function () {
|
||||
if (!a.input)
|
||||
return;
|
||||
return _chunks()[1];
|
||||
});
|
||||
lazy.prop(o, 'signature', function () {
|
||||
if (!a.input)
|
||||
return;
|
||||
return _chunks()[0];
|
||||
});
|
||||
lazy.prop(o, 'input', function () {
|
||||
if (!a.pubkey)
|
||||
return;
|
||||
if (!a.signature)
|
||||
return;
|
||||
return bscript.compile([a.signature, a.pubkey]);
|
||||
});
|
||||
lazy.prop(o, 'witness', function () {
|
||||
if (!o.input)
|
||||
return;
|
||||
return [];
|
||||
});
|
||||
// extended validation
|
||||
if (opts.validate) {
|
||||
let hash = Buffer.from([]);
|
||||
if (a.address) {
|
||||
if (_address().version !== network.pubKeyHash)
|
||||
throw new TypeError('Invalid version or Network mismatch');
|
||||
if (_address().hash.length !== 20)
|
||||
throw new TypeError('Invalid address');
|
||||
hash = _address().hash;
|
||||
}
|
||||
if (a.hash) {
|
||||
if (hash.length > 0 && !hash.equals(a.hash))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = a.hash;
|
||||
}
|
||||
if (a.output) {
|
||||
if (a.output.length !== 25 ||
|
||||
a.output[0] !== OPS.OP_DUP ||
|
||||
a.output[1] !== OPS.OP_HASH160 ||
|
||||
a.output[2] !== 0x14 ||
|
||||
a.output[23] !== OPS.OP_EQUALVERIFY ||
|
||||
a.output[24] !== OPS.OP_CHECKSIG)
|
||||
throw new TypeError('Output is invalid');
|
||||
const hash2 = a.output.slice(3, 23);
|
||||
if (hash.length > 0 && !hash.equals(hash2))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = hash2;
|
||||
}
|
||||
if (a.pubkey) {
|
||||
const pkh = bcrypto.hash160(a.pubkey);
|
||||
if (hash.length > 0 && !hash.equals(pkh))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = pkh;
|
||||
}
|
||||
if (a.input) {
|
||||
const chunks = _chunks();
|
||||
if (chunks.length !== 2)
|
||||
throw new TypeError('Input is invalid');
|
||||
if (!bscript.isCanonicalScriptSignature(chunks[0]))
|
||||
throw new TypeError('Input has invalid signature');
|
||||
if (!ecc.isPoint(chunks[1]))
|
||||
throw new TypeError('Input has invalid pubkey');
|
||||
if (a.signature && !a.signature.equals(chunks[0]))
|
||||
throw new TypeError('Signature mismatch');
|
||||
if (a.pubkey && !a.pubkey.equals(chunks[1]))
|
||||
throw new TypeError('Pubkey mismatch');
|
||||
const pkh = bcrypto.hash160(chunks[1]);
|
||||
if (hash.length > 0 && !hash.equals(pkh))
|
||||
throw new TypeError('Hash mismatch');
|
||||
}
|
||||
}
|
||||
return Object.assign(o, a);
|
||||
}
|
||||
exports.p2pkh = p2pkh;
|
191
src/payments/p2sh.js
Normal file
191
src/payments/p2sh.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const networks_1 = require("../networks"); // eslint-disable-line
|
||||
const bscript = require("../script");
|
||||
const bcrypto = require("../crypto");
|
||||
const lazy = require("./lazy");
|
||||
const typef = require('typeforce');
|
||||
const OPS = bscript.OPS;
|
||||
const bs58check = require('bs58check');
|
||||
function stacksEqual(a, b) {
|
||||
if (a.length !== b.length)
|
||||
return false;
|
||||
return a.every(function (x, i) {
|
||||
return x.equals(b[i]);
|
||||
});
|
||||
}
|
||||
// input: [redeemScriptSig ...] {redeemScript}
|
||||
// witness: <?>
|
||||
// output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL
|
||||
function p2sh(a, opts) {
|
||||
if (!a.address &&
|
||||
!a.hash &&
|
||||
!a.output &&
|
||||
!a.redeem &&
|
||||
!a.input)
|
||||
throw new TypeError('Not enough data');
|
||||
opts = Object.assign({ validate: true }, opts || {});
|
||||
typef({
|
||||
network: typef.maybe(typef.Object),
|
||||
address: typef.maybe(typef.String),
|
||||
hash: typef.maybe(typef.BufferN(20)),
|
||||
output: typef.maybe(typef.BufferN(23)),
|
||||
redeem: typef.maybe({
|
||||
network: typef.maybe(typef.Object),
|
||||
output: typef.maybe(typef.Buffer),
|
||||
input: typef.maybe(typef.Buffer),
|
||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
||||
}),
|
||||
input: typef.maybe(typef.Buffer),
|
||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
||||
}, a);
|
||||
let network = a.network;
|
||||
if (!network) {
|
||||
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
|
||||
}
|
||||
const o = { network };
|
||||
const _address = lazy.value(function () {
|
||||
const payload = bs58check.decode(a.address);
|
||||
const version = payload.readUInt8(0);
|
||||
const hash = payload.slice(1);
|
||||
return { version, hash };
|
||||
});
|
||||
const _chunks = lazy.value(function () { return bscript.decompile(a.input); });
|
||||
const _redeem = lazy.value(function () {
|
||||
const chunks = _chunks();
|
||||
return {
|
||||
network,
|
||||
output: chunks[chunks.length - 1],
|
||||
input: bscript.compile(chunks.slice(0, -1)),
|
||||
witness: a.witness || []
|
||||
};
|
||||
});
|
||||
// output dependents
|
||||
lazy.prop(o, 'address', function () {
|
||||
if (!o.hash)
|
||||
return;
|
||||
const payload = Buffer.allocUnsafe(21);
|
||||
payload.writeUInt8(o.network.scriptHash, 0);
|
||||
o.hash.copy(payload, 1);
|
||||
return bs58check.encode(payload);
|
||||
});
|
||||
lazy.prop(o, 'hash', function () {
|
||||
// in order of least effort
|
||||
if (a.output)
|
||||
return a.output.slice(2, 22);
|
||||
if (a.address)
|
||||
return _address().hash;
|
||||
if (o.redeem && o.redeem.output)
|
||||
return bcrypto.hash160(o.redeem.output);
|
||||
});
|
||||
lazy.prop(o, 'output', function () {
|
||||
if (!o.hash)
|
||||
return;
|
||||
return bscript.compile([
|
||||
OPS.OP_HASH160,
|
||||
o.hash,
|
||||
OPS.OP_EQUAL
|
||||
]);
|
||||
});
|
||||
// input dependents
|
||||
lazy.prop(o, 'redeem', function () {
|
||||
if (!a.input)
|
||||
return;
|
||||
return _redeem();
|
||||
});
|
||||
lazy.prop(o, 'input', function () {
|
||||
if (!a.redeem || !a.redeem.input || !a.redeem.output)
|
||||
return;
|
||||
return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output));
|
||||
});
|
||||
lazy.prop(o, 'witness', function () {
|
||||
if (o.redeem && o.redeem.witness)
|
||||
return o.redeem.witness;
|
||||
if (o.input)
|
||||
return [];
|
||||
});
|
||||
if (opts.validate) {
|
||||
let hash = Buffer.from([]);
|
||||
if (a.address) {
|
||||
if (_address().version !== network.scriptHash)
|
||||
throw new TypeError('Invalid version or Network mismatch');
|
||||
if (_address().hash.length !== 20)
|
||||
throw new TypeError('Invalid address');
|
||||
hash = _address().hash;
|
||||
}
|
||||
if (a.hash) {
|
||||
if (hash.length > 0 && !hash.equals(a.hash))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = a.hash;
|
||||
}
|
||||
if (a.output) {
|
||||
if (a.output.length !== 23 ||
|
||||
a.output[0] !== OPS.OP_HASH160 ||
|
||||
a.output[1] !== 0x14 ||
|
||||
a.output[22] !== OPS.OP_EQUAL)
|
||||
throw new TypeError('Output is invalid');
|
||||
const hash2 = a.output.slice(2, 22);
|
||||
if (hash.length > 0 && !hash.equals(hash2))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = hash2;
|
||||
}
|
||||
// inlined to prevent 'no-inner-declarations' failing
|
||||
const checkRedeem = function (redeem) {
|
||||
// is the redeem output empty/invalid?
|
||||
if (redeem.output) {
|
||||
const decompile = bscript.decompile(redeem.output);
|
||||
if (!decompile || decompile.length < 1)
|
||||
throw new TypeError('Redeem.output too short');
|
||||
// match hash against other sources
|
||||
const hash2 = bcrypto.hash160(redeem.output);
|
||||
if (hash.length > 0 && !hash.equals(hash2))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = hash2;
|
||||
}
|
||||
if (redeem.input) {
|
||||
const hasInput = redeem.input.length > 0;
|
||||
const hasWitness = redeem.witness && redeem.witness.length > 0;
|
||||
if (!hasInput && !hasWitness)
|
||||
throw new TypeError('Empty input');
|
||||
if (hasInput && hasWitness)
|
||||
throw new TypeError('Input and witness provided');
|
||||
if (hasInput) {
|
||||
const richunks = bscript.decompile(redeem.input);
|
||||
if (!bscript.isPushOnly(richunks))
|
||||
throw new TypeError('Non push-only scriptSig');
|
||||
}
|
||||
}
|
||||
};
|
||||
if (a.input) {
|
||||
const chunks = _chunks();
|
||||
if (!chunks || chunks.length < 1)
|
||||
throw new TypeError('Input too short');
|
||||
if (!Buffer.isBuffer(_redeem().output))
|
||||
throw new TypeError('Input is invalid');
|
||||
checkRedeem(_redeem());
|
||||
}
|
||||
if (a.redeem) {
|
||||
if (a.redeem.network && a.redeem.network !== network)
|
||||
throw new TypeError('Network mismatch');
|
||||
if (a.input) {
|
||||
const redeem = _redeem();
|
||||
if (a.redeem.output && !a.redeem.output.equals(redeem.output))
|
||||
throw new TypeError('Redeem.output mismatch');
|
||||
if (a.redeem.input && !a.redeem.input.equals(redeem.input))
|
||||
throw new TypeError('Redeem.input mismatch');
|
||||
}
|
||||
checkRedeem(a.redeem);
|
||||
}
|
||||
if (a.witness) {
|
||||
if (a.redeem &&
|
||||
a.redeem.witness &&
|
||||
!stacksEqual(a.redeem.witness, a.witness))
|
||||
throw new TypeError('Witness and redeem.witness mismatch');
|
||||
}
|
||||
}
|
||||
return Object.assign(o, a);
|
||||
}
|
||||
exports.p2sh = p2sh;
|
145
src/payments/p2wpkh.js
Normal file
145
src/payments/p2wpkh.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../script");
|
||||
const bcrypto = require("../crypto");
|
||||
const lazy = require("./lazy");
|
||||
const networks_1 = require("../networks");
|
||||
const typef = require('typeforce');
|
||||
const OPS = bscript.OPS;
|
||||
const ecc = require('tiny-secp256k1');
|
||||
const bech32 = require('bech32');
|
||||
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||
// witness: {signature} {pubKey}
|
||||
// input: <>
|
||||
// output: OP_0 {pubKeyHash}
|
||||
function p2wpkh(a, opts) {
|
||||
if (!a.address &&
|
||||
!a.hash &&
|
||||
!a.output &&
|
||||
!a.pubkey &&
|
||||
!a.witness)
|
||||
throw new TypeError('Not enough data');
|
||||
opts = Object.assign({ validate: true }, opts || {});
|
||||
typef({
|
||||
address: typef.maybe(typef.String),
|
||||
hash: typef.maybe(typef.BufferN(20)),
|
||||
input: typef.maybe(typef.BufferN(0)),
|
||||
network: typef.maybe(typef.Object),
|
||||
output: typef.maybe(typef.BufferN(22)),
|
||||
pubkey: typef.maybe(ecc.isPoint),
|
||||
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
||||
}, a);
|
||||
const _address = lazy.value(function () {
|
||||
const result = bech32.decode(a.address);
|
||||
const version = result.words.shift();
|
||||
const data = bech32.fromWords(result.words);
|
||||
return {
|
||||
version,
|
||||
prefix: result.prefix,
|
||||
data: Buffer.from(data)
|
||||
};
|
||||
});
|
||||
const network = a.network || networks_1.bitcoin;
|
||||
const o = { network };
|
||||
lazy.prop(o, 'address', function () {
|
||||
if (!o.hash)
|
||||
return;
|
||||
const words = bech32.toWords(o.hash);
|
||||
words.unshift(0x00);
|
||||
return bech32.encode(network.bech32, words);
|
||||
});
|
||||
lazy.prop(o, 'hash', function () {
|
||||
if (a.output)
|
||||
return a.output.slice(2, 22);
|
||||
if (a.address)
|
||||
return _address().data;
|
||||
if (a.pubkey || o.pubkey)
|
||||
return bcrypto.hash160(a.pubkey || o.pubkey); // eslint-disable-line
|
||||
});
|
||||
lazy.prop(o, 'output', function () {
|
||||
if (!o.hash)
|
||||
return;
|
||||
return bscript.compile([
|
||||
OPS.OP_0,
|
||||
o.hash
|
||||
]);
|
||||
});
|
||||
lazy.prop(o, 'pubkey', function () {
|
||||
if (a.pubkey)
|
||||
return a.pubkey;
|
||||
if (!a.witness)
|
||||
return;
|
||||
return a.witness[1];
|
||||
});
|
||||
lazy.prop(o, 'signature', function () {
|
||||
if (!a.witness)
|
||||
return;
|
||||
return a.witness[0];
|
||||
});
|
||||
lazy.prop(o, 'input', function () {
|
||||
if (!o.witness)
|
||||
return;
|
||||
return EMPTY_BUFFER;
|
||||
});
|
||||
lazy.prop(o, 'witness', function () {
|
||||
if (!a.pubkey)
|
||||
return;
|
||||
if (!a.signature)
|
||||
return;
|
||||
return [a.signature, a.pubkey];
|
||||
});
|
||||
// extended validation
|
||||
if (opts.validate) {
|
||||
let hash = Buffer.from([]);
|
||||
if (a.address) {
|
||||
if (network && network.bech32 !== _address().prefix)
|
||||
throw new TypeError('Invalid prefix or Network mismatch');
|
||||
if (_address().version !== 0x00)
|
||||
throw new TypeError('Invalid address version');
|
||||
if (_address().data.length !== 20)
|
||||
throw new TypeError('Invalid address data');
|
||||
hash = _address().data;
|
||||
}
|
||||
if (a.hash) {
|
||||
if (hash.length > 0 && !hash.equals(a.hash))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = a.hash;
|
||||
}
|
||||
if (a.output) {
|
||||
if (a.output.length !== 22 ||
|
||||
a.output[0] !== OPS.OP_0 ||
|
||||
a.output[1] !== 0x14)
|
||||
throw new TypeError('Output is invalid');
|
||||
if (hash.length > 0 && !hash.equals(a.output.slice(2)))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = a.output.slice(2);
|
||||
}
|
||||
if (a.pubkey) {
|
||||
const pkh = bcrypto.hash160(a.pubkey);
|
||||
if (hash.length > 0 && !hash.equals(pkh))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = pkh;
|
||||
}
|
||||
if (a.witness) {
|
||||
if (a.witness.length !== 2)
|
||||
throw new TypeError('Witness is invalid');
|
||||
if (!bscript.isCanonicalScriptSignature(a.witness[0]))
|
||||
throw new TypeError('Witness has invalid signature');
|
||||
if (!ecc.isPoint(a.witness[1]))
|
||||
throw new TypeError('Witness has invalid pubkey');
|
||||
if (a.signature && !a.signature.equals(a.witness[0]))
|
||||
throw new TypeError('Signature mismatch');
|
||||
if (a.pubkey && !a.pubkey.equals(a.witness[1]))
|
||||
throw new TypeError('Pubkey mismatch');
|
||||
const pkh = bcrypto.hash160(a.witness[1]);
|
||||
if (hash.length > 0 && !hash.equals(pkh))
|
||||
throw new TypeError('Hash mismatch');
|
||||
}
|
||||
}
|
||||
return Object.assign(o, a);
|
||||
}
|
||||
exports.p2wpkh = p2wpkh;
|
178
src/payments/p2wsh.js
Normal file
178
src/payments/p2wsh.js
Normal file
|
@ -0,0 +1,178 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const networks_1 = require("../networks"); // eslint-disable-line
|
||||
const bscript = require("../script");
|
||||
const bcrypto = require("../crypto");
|
||||
const lazy = require("./lazy");
|
||||
const typef = require('typeforce');
|
||||
const OPS = bscript.OPS;
|
||||
const bech32 = require('bech32');
|
||||
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||
function stacksEqual(a, b) {
|
||||
if (a.length !== b.length)
|
||||
return false;
|
||||
return a.every(function (x, i) {
|
||||
return x.equals(b[i]);
|
||||
});
|
||||
}
|
||||
// input: <>
|
||||
// witness: [redeemScriptSig ...] {redeemScript}
|
||||
// output: OP_0 {sha256(redeemScript)}
|
||||
function p2wsh(a, opts) {
|
||||
if (!a.address &&
|
||||
!a.hash &&
|
||||
!a.output &&
|
||||
!a.redeem &&
|
||||
!a.witness)
|
||||
throw new TypeError('Not enough data');
|
||||
opts = Object.assign({ validate: true }, opts || {});
|
||||
typef({
|
||||
network: typef.maybe(typef.Object),
|
||||
address: typef.maybe(typef.String),
|
||||
hash: typef.maybe(typef.BufferN(32)),
|
||||
output: typef.maybe(typef.BufferN(34)),
|
||||
redeem: typef.maybe({
|
||||
input: typef.maybe(typef.Buffer),
|
||||
network: typef.maybe(typef.Object),
|
||||
output: typef.maybe(typef.Buffer),
|
||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
||||
}),
|
||||
input: typef.maybe(typef.BufferN(0)),
|
||||
witness: typef.maybe(typef.arrayOf(typef.Buffer))
|
||||
}, a);
|
||||
const _address = lazy.value(function () {
|
||||
const result = bech32.decode(a.address);
|
||||
const version = result.words.shift();
|
||||
const data = bech32.fromWords(result.words);
|
||||
return {
|
||||
version,
|
||||
prefix: result.prefix,
|
||||
data: Buffer.from(data)
|
||||
};
|
||||
});
|
||||
const _rchunks = lazy.value(function () { return bscript.decompile(a.redeem.input); });
|
||||
let network = a.network;
|
||||
if (!network) {
|
||||
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
|
||||
}
|
||||
const o = { network };
|
||||
lazy.prop(o, 'address', function () {
|
||||
if (!o.hash)
|
||||
return;
|
||||
const words = bech32.toWords(o.hash);
|
||||
words.unshift(0x00);
|
||||
return bech32.encode(network.bech32, words);
|
||||
});
|
||||
lazy.prop(o, 'hash', function () {
|
||||
if (a.output)
|
||||
return a.output.slice(2);
|
||||
if (a.address)
|
||||
return _address().data;
|
||||
if (o.redeem && o.redeem.output)
|
||||
return bcrypto.sha256(o.redeem.output);
|
||||
});
|
||||
lazy.prop(o, 'output', function () {
|
||||
if (!o.hash)
|
||||
return;
|
||||
return bscript.compile([
|
||||
OPS.OP_0,
|
||||
o.hash
|
||||
]);
|
||||
});
|
||||
lazy.prop(o, 'redeem', function () {
|
||||
if (!a.witness)
|
||||
return;
|
||||
return {
|
||||
output: a.witness[a.witness.length - 1],
|
||||
input: EMPTY_BUFFER,
|
||||
witness: a.witness.slice(0, -1)
|
||||
};
|
||||
});
|
||||
lazy.prop(o, 'input', function () {
|
||||
if (!o.witness)
|
||||
return;
|
||||
return EMPTY_BUFFER;
|
||||
});
|
||||
lazy.prop(o, 'witness', function () {
|
||||
// transform redeem input to witness stack?
|
||||
if (a.redeem &&
|
||||
a.redeem.input &&
|
||||
a.redeem.input.length > 0 &&
|
||||
a.redeem.output &&
|
||||
a.redeem.output.length > 0) {
|
||||
const stack = bscript.toStack(_rchunks());
|
||||
// assign, and blank the existing input
|
||||
o.redeem = Object.assign({ witness: stack }, a.redeem);
|
||||
o.redeem.input = EMPTY_BUFFER;
|
||||
return [].concat(stack, a.redeem.output);
|
||||
}
|
||||
if (!a.redeem)
|
||||
return;
|
||||
if (!a.redeem.output)
|
||||
return;
|
||||
if (!a.redeem.witness)
|
||||
return;
|
||||
return [].concat(a.redeem.witness, a.redeem.output);
|
||||
});
|
||||
// extended validation
|
||||
if (opts.validate) {
|
||||
let hash = Buffer.from([]);
|
||||
if (a.address) {
|
||||
if (_address().prefix !== network.bech32)
|
||||
throw new TypeError('Invalid prefix or Network mismatch');
|
||||
if (_address().version !== 0x00)
|
||||
throw new TypeError('Invalid address version');
|
||||
if (_address().data.length !== 32)
|
||||
throw new TypeError('Invalid address data');
|
||||
hash = _address().data;
|
||||
}
|
||||
if (a.hash) {
|
||||
if (hash.length > 0 && !hash.equals(a.hash))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = a.hash;
|
||||
}
|
||||
if (a.output) {
|
||||
if (a.output.length !== 34 ||
|
||||
a.output[0] !== OPS.OP_0 ||
|
||||
a.output[1] !== 0x20)
|
||||
throw new TypeError('Output is invalid');
|
||||
const hash2 = a.output.slice(2);
|
||||
if (hash.length > 0 && !hash.equals(hash2))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = hash2;
|
||||
}
|
||||
if (a.redeem) {
|
||||
if (a.redeem.network && a.redeem.network !== network)
|
||||
throw new TypeError('Network mismatch');
|
||||
// is there two redeem sources?
|
||||
if (a.redeem.input &&
|
||||
a.redeem.input.length > 0 &&
|
||||
a.redeem.witness &&
|
||||
a.redeem.witness.length > 0)
|
||||
throw new TypeError('Ambiguous witness source');
|
||||
// is the redeem output non-empty?
|
||||
if (a.redeem.output) {
|
||||
if (bscript.decompile(a.redeem.output).length === 0)
|
||||
throw new TypeError('Redeem.output is invalid');
|
||||
// match hash against other sources
|
||||
const hash2 = bcrypto.sha256(a.redeem.output);
|
||||
if (hash.length > 0 && !hash.equals(hash2))
|
||||
throw new TypeError('Hash mismatch');
|
||||
else
|
||||
hash = hash2;
|
||||
}
|
||||
if (a.redeem.input && !bscript.isPushOnly(_rchunks()))
|
||||
throw new TypeError('Non push-only scriptSig');
|
||||
if (a.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness))
|
||||
throw new TypeError('Witness and redeem.witness mismatch');
|
||||
}
|
||||
if (a.witness) {
|
||||
if (a.redeem && a.redeem.output && !a.redeem.output.equals(a.witness[a.witness.length - 1]))
|
||||
throw new TypeError('Witness and redeem.output mismatch');
|
||||
}
|
||||
}
|
||||
return Object.assign(o, a);
|
||||
}
|
||||
exports.p2wsh = p2wsh;
|
188
src/script.js
Normal file
188
src/script.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const types = require("./types");
|
||||
const scriptNumber = require("./script_number");
|
||||
const scriptSignature = require("./script_signature");
|
||||
const bip66 = require('bip66');
|
||||
const ecc = require('tiny-secp256k1');
|
||||
const pushdata = require('pushdata-bitcoin');
|
||||
const typeforce = require('typeforce');
|
||||
exports.OPS = require('bitcoin-ops');
|
||||
const REVERSE_OPS = require('bitcoin-ops/map');
|
||||
const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1
|
||||
function isOPInt(value) {
|
||||
return types.Number(value) &&
|
||||
((value === exports.OPS.OP_0) ||
|
||||
(value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) ||
|
||||
(value === exports.OPS.OP_1NEGATE));
|
||||
}
|
||||
function isPushOnlyChunk(value) {
|
||||
return types.Buffer(value) || isOPInt(value);
|
||||
}
|
||||
function isPushOnly(value) {
|
||||
return types.Array(value) && value.every(isPushOnlyChunk);
|
||||
}
|
||||
exports.isPushOnly = isPushOnly;
|
||||
function asMinimalOP(buffer) {
|
||||
if (buffer.length === 0)
|
||||
return exports.OPS.OP_0;
|
||||
if (buffer.length !== 1)
|
||||
return;
|
||||
if (buffer[0] >= 1 && buffer[0] <= 16)
|
||||
return OP_INT_BASE + buffer[0];
|
||||
if (buffer[0] === 0x81)
|
||||
return exports.OPS.OP_1NEGATE;
|
||||
}
|
||||
function chunksIsBuffer(buf) {
|
||||
return Buffer.isBuffer(buf);
|
||||
}
|
||||
function chunksIsArray(buf) {
|
||||
return types.Array(buf);
|
||||
}
|
||||
function singleChunkIsBuffer(buf) {
|
||||
return Buffer.isBuffer(buf);
|
||||
}
|
||||
function compile(chunks) {
|
||||
// TODO: remove me
|
||||
if (chunksIsBuffer(chunks))
|
||||
return chunks;
|
||||
typeforce(types.Array, chunks);
|
||||
const bufferSize = chunks.reduce(function (accum, chunk) {
|
||||
// data chunk
|
||||
if (singleChunkIsBuffer(chunk)) {
|
||||
// adhere to BIP62.3, minimal push policy
|
||||
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) {
|
||||
return accum + 1;
|
||||
}
|
||||
return accum + pushdata.encodingLength(chunk.length) + chunk.length;
|
||||
}
|
||||
// opcode
|
||||
return accum + 1;
|
||||
}, 0.0);
|
||||
const buffer = Buffer.allocUnsafe(bufferSize);
|
||||
let offset = 0;
|
||||
chunks.forEach(function (chunk) {
|
||||
// data chunk
|
||||
if (singleChunkIsBuffer(chunk)) {
|
||||
// adhere to BIP62.3, minimal push policy
|
||||
const opcode = asMinimalOP(chunk);
|
||||
if (opcode !== undefined) {
|
||||
buffer.writeUInt8(opcode, offset);
|
||||
offset += 1;
|
||||
return;
|
||||
}
|
||||
offset += pushdata.encode(buffer, chunk.length, offset);
|
||||
chunk.copy(buffer, offset);
|
||||
offset += chunk.length;
|
||||
// opcode
|
||||
}
|
||||
else {
|
||||
buffer.writeUInt8(chunk, offset);
|
||||
offset += 1;
|
||||
}
|
||||
});
|
||||
if (offset !== buffer.length)
|
||||
throw new Error('Could not decode chunks');
|
||||
return buffer;
|
||||
}
|
||||
exports.compile = compile;
|
||||
function decompile(buffer) {
|
||||
// TODO: remove me
|
||||
if (chunksIsArray(buffer))
|
||||
return buffer;
|
||||
typeforce(types.Buffer, buffer);
|
||||
const chunks = [];
|
||||
let i = 0;
|
||||
while (i < buffer.length) {
|
||||
const opcode = buffer[i];
|
||||
// data chunk
|
||||
if ((opcode > exports.OPS.OP_0) && (opcode <= exports.OPS.OP_PUSHDATA4)) {
|
||||
const d = pushdata.decode(buffer, i);
|
||||
// did reading a pushDataInt fail?
|
||||
if (d === null)
|
||||
return null;
|
||||
i += d.size;
|
||||
// attempt to read too much data?
|
||||
if (i + d.number > buffer.length)
|
||||
return null;
|
||||
const data = buffer.slice(i, i + d.number);
|
||||
i += d.number;
|
||||
// decompile minimally
|
||||
const op = asMinimalOP(data);
|
||||
if (op !== undefined) {
|
||||
chunks.push(op);
|
||||
}
|
||||
else {
|
||||
chunks.push(data);
|
||||
}
|
||||
// opcode
|
||||
}
|
||||
else {
|
||||
chunks.push(opcode);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
exports.decompile = decompile;
|
||||
function toASM(chunks) {
|
||||
if (chunksIsBuffer(chunks)) {
|
||||
chunks = decompile(chunks);
|
||||
}
|
||||
return chunks.map(function (chunk) {
|
||||
// data?
|
||||
if (singleChunkIsBuffer(chunk)) {
|
||||
const op = asMinimalOP(chunk);
|
||||
if (op === undefined)
|
||||
return chunk.toString('hex');
|
||||
chunk = op;
|
||||
}
|
||||
// opcode!
|
||||
return REVERSE_OPS[chunk];
|
||||
}).join(' ');
|
||||
}
|
||||
exports.toASM = toASM;
|
||||
function fromASM(asm) {
|
||||
typeforce(types.String, asm);
|
||||
return compile(asm.split(' ').map(function (chunkStr) {
|
||||
// opcode?
|
||||
if (exports.OPS[chunkStr] !== undefined)
|
||||
return exports.OPS[chunkStr];
|
||||
typeforce(types.Hex, chunkStr);
|
||||
// data!
|
||||
return Buffer.from(chunkStr, 'hex');
|
||||
}));
|
||||
}
|
||||
exports.fromASM = fromASM;
|
||||
function toStack(chunks) {
|
||||
chunks = decompile(chunks);
|
||||
typeforce(isPushOnly, chunks);
|
||||
return chunks.map(function (op) {
|
||||
if (singleChunkIsBuffer(op))
|
||||
return op;
|
||||
if (op === exports.OPS.OP_0)
|
||||
return Buffer.allocUnsafe(0);
|
||||
return scriptNumber.encode(op - OP_INT_BASE);
|
||||
});
|
||||
}
|
||||
exports.toStack = toStack;
|
||||
function isCanonicalPubKey(buffer) {
|
||||
return ecc.isPoint(buffer);
|
||||
}
|
||||
exports.isCanonicalPubKey = isCanonicalPubKey;
|
||||
function isDefinedHashType(hashType) {
|
||||
const hashTypeMod = hashType & ~0x80;
|
||||
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
||||
return hashTypeMod > 0x00 && hashTypeMod < 0x04;
|
||||
}
|
||||
exports.isDefinedHashType = isDefinedHashType;
|
||||
function isCanonicalScriptSignature(buffer) {
|
||||
if (!Buffer.isBuffer(buffer))
|
||||
return false;
|
||||
if (!isDefinedHashType(buffer[buffer.length - 1]))
|
||||
return false;
|
||||
return bip66.check(buffer.slice(0, -1));
|
||||
}
|
||||
exports.isCanonicalScriptSignature = isCanonicalScriptSignature;
|
||||
exports.number = scriptNumber;
|
||||
exports.signature = scriptSignature;
|
60
src/script_number.js
Normal file
60
src/script_number.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
function decode(buffer, maxLength, minimal) {
|
||||
maxLength = maxLength || 4;
|
||||
minimal = minimal === undefined ? true : minimal;
|
||||
const length = buffer.length;
|
||||
if (length === 0)
|
||||
return 0;
|
||||
if (length > maxLength)
|
||||
throw new TypeError('Script number overflow');
|
||||
if (minimal) {
|
||||
if ((buffer[length - 1] & 0x7f) === 0) {
|
||||
if (length <= 1 || (buffer[length - 2] & 0x80) === 0)
|
||||
throw new Error('Non-minimally encoded script number');
|
||||
}
|
||||
}
|
||||
// 40-bit
|
||||
if (length === 5) {
|
||||
const a = buffer.readUInt32LE(0);
|
||||
const b = buffer.readUInt8(4);
|
||||
if (b & 0x80)
|
||||
return -(((b & ~0x80) * 0x100000000) + a);
|
||||
return (b * 0x100000000) + a;
|
||||
}
|
||||
// 32-bit / 24-bit / 16-bit / 8-bit
|
||||
let result = 0;
|
||||
for (var i = 0; i < length; ++i) {
|
||||
result |= buffer[i] << (8 * i);
|
||||
}
|
||||
if (buffer[length - 1] & 0x80)
|
||||
return -(result & ~(0x80 << (8 * (length - 1))));
|
||||
return result;
|
||||
}
|
||||
exports.decode = decode;
|
||||
function scriptNumSize(i) {
|
||||
return i > 0x7fffffff ? 5
|
||||
: i > 0x7fffff ? 4
|
||||
: i > 0x7fff ? 3
|
||||
: i > 0x7f ? 2
|
||||
: i > 0x00 ? 1
|
||||
: 0;
|
||||
}
|
||||
function encode(number) {
|
||||
let value = Math.abs(number);
|
||||
const size = scriptNumSize(value);
|
||||
const buffer = Buffer.allocUnsafe(size);
|
||||
const negative = number < 0;
|
||||
for (var i = 0; i < size; ++i) {
|
||||
buffer.writeUInt8(value & 0xff, i);
|
||||
value >>= 8;
|
||||
}
|
||||
if (buffer[size - 1] & 0x80) {
|
||||
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1);
|
||||
}
|
||||
else if (negative) {
|
||||
buffer[size - 1] |= 0x80;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
exports.encode = encode;
|
58
src/script_signature.js
Normal file
58
src/script_signature.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const types = require("./types");
|
||||
const bip66 = require('bip66');
|
||||
const typeforce = require('typeforce');
|
||||
const ZERO = Buffer.alloc(1, 0);
|
||||
function toDER(x) {
|
||||
let i = 0;
|
||||
while (x[i] === 0)
|
||||
++i;
|
||||
if (i === x.length)
|
||||
return ZERO;
|
||||
x = x.slice(i);
|
||||
if (x[0] & 0x80)
|
||||
return Buffer.concat([ZERO, x], 1 + x.length);
|
||||
return x;
|
||||
}
|
||||
function fromDER(x) {
|
||||
if (x[0] === 0x00)
|
||||
x = x.slice(1);
|
||||
const buffer = Buffer.alloc(32, 0);
|
||||
const bstart = Math.max(0, 32 - x.length);
|
||||
x.copy(buffer, bstart);
|
||||
return buffer;
|
||||
}
|
||||
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
|
||||
function decode(buffer) {
|
||||
const hashType = buffer.readUInt8(buffer.length - 1);
|
||||
const hashTypeMod = hashType & ~0x80;
|
||||
if (hashTypeMod <= 0 || hashTypeMod >= 4)
|
||||
throw new Error('Invalid hashType ' + hashType);
|
||||
const decode = bip66.decode(buffer.slice(0, -1));
|
||||
const r = fromDER(decode.r);
|
||||
const s = fromDER(decode.s);
|
||||
return {
|
||||
signature: Buffer.concat([r, s], 64),
|
||||
hashType: hashType
|
||||
};
|
||||
}
|
||||
exports.decode = decode;
|
||||
function encode(signature, hashType) {
|
||||
typeforce({
|
||||
signature: types.BufferN(64),
|
||||
hashType: types.UInt8
|
||||
}, { signature, hashType });
|
||||
const hashTypeMod = hashType & ~0x80;
|
||||
if (hashTypeMod <= 0 || hashTypeMod >= 4)
|
||||
throw new Error('Invalid hashType ' + hashType);
|
||||
const hashTypeBuffer = Buffer.allocUnsafe(1);
|
||||
hashTypeBuffer.writeUInt8(hashType, 0);
|
||||
const r = toDER(signature.slice(0, 32));
|
||||
const s = toDER(signature.slice(32, 64));
|
||||
return Buffer.concat([
|
||||
bip66.encode(r, s),
|
||||
hashTypeBuffer
|
||||
]);
|
||||
}
|
||||
exports.encode = encode;
|
6
src/templates/multisig/index.js
Normal file
6
src/templates/multisig/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const input = require("./input");
|
||||
exports.input = input;
|
||||
const output = require("./output");
|
||||
exports.output = output;
|
21
src/templates/multisig/input.js
Normal file
21
src/templates/multisig/input.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
"use strict";
|
||||
// OP_0 [signatures ...]
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const script_1 = require("../../script");
|
||||
function partialSignature(value) {
|
||||
return value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value);
|
||||
}
|
||||
function check(script, allowIncomplete) {
|
||||
const chunks = bscript.decompile(script);
|
||||
if (chunks.length < 2)
|
||||
return false;
|
||||
if (chunks[0] !== script_1.OPS.OP_0)
|
||||
return false;
|
||||
if (allowIncomplete) {
|
||||
return chunks.slice(1).every(partialSignature);
|
||||
}
|
||||
return chunks.slice(1).every(bscript.isCanonicalScriptSignature);
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'multisig input'; };
|
34
src/templates/multisig/output.js
Normal file
34
src/templates/multisig/output.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
"use strict";
|
||||
// m [pubKeys ...] n OP_CHECKMULTISIG
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const types = require("../../types");
|
||||
const script_1 = require("../../script");
|
||||
const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1
|
||||
function check(script, allowIncomplete) {
|
||||
const chunks = bscript.decompile(script);
|
||||
if (chunks.length < 4)
|
||||
return false;
|
||||
if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG)
|
||||
return false;
|
||||
if (!types.Number(chunks[0]))
|
||||
return false;
|
||||
if (!types.Number(chunks[chunks.length - 2]))
|
||||
return false;
|
||||
const m = chunks[0] - OP_INT_BASE;
|
||||
const n = chunks[chunks.length - 2] - OP_INT_BASE;
|
||||
if (m <= 0)
|
||||
return false;
|
||||
if (n > 16)
|
||||
return false;
|
||||
if (m > n)
|
||||
return false;
|
||||
if (n !== chunks.length - 3)
|
||||
return false;
|
||||
if (allowIncomplete)
|
||||
return true;
|
||||
const keys = chunks.slice(1, -2);
|
||||
return keys.every(bscript.isCanonicalPubKey);
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'multi-sig output'; };
|
14
src/templates/nulldata.js
Normal file
14
src/templates/nulldata.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// OP_RETURN {data}
|
||||
const bscript = require("../script");
|
||||
const OPS = bscript.OPS;
|
||||
function check(script) {
|
||||
const buffer = bscript.compile(script);
|
||||
return buffer.length > 1 &&
|
||||
buffer[0] === OPS.OP_RETURN;
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'null data output'; };
|
||||
const output = { check };
|
||||
exports.output = output;
|
6
src/templates/pubkey/index.js
Normal file
6
src/templates/pubkey/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const input = require("./input");
|
||||
exports.input = input;
|
||||
const output = require("./output");
|
||||
exports.output = output;
|
11
src/templates/pubkey/input.js
Normal file
11
src/templates/pubkey/input.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
"use strict";
|
||||
// {signature}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
function check(script) {
|
||||
const chunks = bscript.decompile(script);
|
||||
return chunks.length === 1 &&
|
||||
bscript.isCanonicalScriptSignature(chunks[0]);
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'pubKey input'; };
|
13
src/templates/pubkey/output.js
Normal file
13
src/templates/pubkey/output.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
"use strict";
|
||||
// {pubKey} OP_CHECKSIG
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const script_1 = require("../../script");
|
||||
function check(script) {
|
||||
const chunks = bscript.decompile(script);
|
||||
return chunks.length === 2 &&
|
||||
bscript.isCanonicalPubKey(chunks[0]) &&
|
||||
chunks[1] === script_1.OPS.OP_CHECKSIG;
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'pubKey output'; };
|
6
src/templates/pubkeyhash/index.js
Normal file
6
src/templates/pubkeyhash/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const input = require("./input");
|
||||
exports.input = input;
|
||||
const output = require("./output");
|
||||
exports.output = output;
|
12
src/templates/pubkeyhash/input.js
Normal file
12
src/templates/pubkeyhash/input.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
"use strict";
|
||||
// {signature} {pubKey}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
function check(script) {
|
||||
const chunks = bscript.decompile(script);
|
||||
return chunks.length === 2 &&
|
||||
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
||||
bscript.isCanonicalPubKey(chunks[1]);
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'pubKeyHash input'; };
|
16
src/templates/pubkeyhash/output.js
Normal file
16
src/templates/pubkeyhash/output.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
"use strict";
|
||||
// OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const script_1 = require("../../script");
|
||||
function check(script) {
|
||||
const buffer = bscript.compile(script);
|
||||
return buffer.length === 25 &&
|
||||
buffer[0] === script_1.OPS.OP_DUP &&
|
||||
buffer[1] === script_1.OPS.OP_HASH160 &&
|
||||
buffer[2] === 0x14 &&
|
||||
buffer[23] === script_1.OPS.OP_EQUALVERIFY &&
|
||||
buffer[24] === script_1.OPS.OP_CHECKSIG;
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'pubKeyHash output'; };
|
6
src/templates/scripthash/index.js
Normal file
6
src/templates/scripthash/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const input = require("./input");
|
||||
exports.input = input;
|
||||
const output = require("./output");
|
||||
exports.output = output;
|
43
src/templates/scripthash/input.js
Normal file
43
src/templates/scripthash/input.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
"use strict";
|
||||
// <scriptSig> {serialized scriptPubKey script}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const p2ms = require("../multisig");
|
||||
const p2pk = require("../pubkey");
|
||||
const p2pkh = require("../pubkeyhash");
|
||||
const p2wpkho = require("../witnesspubkeyhash/output");
|
||||
const p2wsho = require("../witnessscripthash/output");
|
||||
function check(script, allowIncomplete) {
|
||||
const chunks = bscript.decompile(script);
|
||||
if (chunks.length < 1)
|
||||
return false;
|
||||
const lastChunk = chunks[chunks.length - 1];
|
||||
if (!Buffer.isBuffer(lastChunk))
|
||||
return false;
|
||||
const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1)));
|
||||
const redeemScriptChunks = bscript.decompile(lastChunk);
|
||||
// is redeemScript a valid script?
|
||||
if (!redeemScriptChunks)
|
||||
return false;
|
||||
// is redeemScriptSig push only?
|
||||
if (!bscript.isPushOnly(scriptSigChunks))
|
||||
return false;
|
||||
// is witness?
|
||||
if (chunks.length === 1) {
|
||||
return p2wsho.check(redeemScriptChunks) ||
|
||||
p2wpkho.check(redeemScriptChunks);
|
||||
}
|
||||
// match types
|
||||
if (p2pkh.input.check(scriptSigChunks) &&
|
||||
p2pkh.output.check(redeemScriptChunks))
|
||||
return true;
|
||||
if (p2ms.input.check(scriptSigChunks, allowIncomplete) &&
|
||||
p2ms.output.check(redeemScriptChunks))
|
||||
return true;
|
||||
if (p2pk.input.check(scriptSigChunks) &&
|
||||
p2pk.output.check(redeemScriptChunks))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'scriptHash input'; };
|
14
src/templates/scripthash/output.js
Normal file
14
src/templates/scripthash/output.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
"use strict";
|
||||
// OP_HASH160 {scriptHash} OP_EQUAL
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const script_1 = require("../../script");
|
||||
function check(script) {
|
||||
const buffer = bscript.compile(script);
|
||||
return buffer.length === 23 &&
|
||||
buffer[0] === script_1.OPS.OP_HASH160 &&
|
||||
buffer[1] === 0x14 &&
|
||||
buffer[22] === script_1.OPS.OP_EQUAL;
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'scriptHash output'; };
|
4
src/templates/witnesscommitment/index.js
Normal file
4
src/templates/witnesscommitment/index.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const output = require("./output");
|
||||
exports.output = output;
|
30
src/templates/witnesscommitment/output.js
Normal file
30
src/templates/witnesscommitment/output.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
"use strict";
|
||||
// OP_RETURN {aa21a9ed} {commitment}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const types = require("../../types");
|
||||
const typeforce = require('typeforce');
|
||||
const script_1 = require("../../script");
|
||||
const HEADER = Buffer.from('aa21a9ed', 'hex');
|
||||
function check(script) {
|
||||
const buffer = bscript.compile(script);
|
||||
return buffer.length > 37 &&
|
||||
buffer[0] === script_1.OPS.OP_RETURN &&
|
||||
buffer[1] === 0x24 &&
|
||||
buffer.slice(2, 6).equals(HEADER);
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'Witness commitment output'; };
|
||||
function encode(commitment) {
|
||||
typeforce(types.Hash256bit, commitment);
|
||||
const buffer = Buffer.allocUnsafe(36);
|
||||
HEADER.copy(buffer, 0);
|
||||
commitment.copy(buffer, 4);
|
||||
return bscript.compile([script_1.OPS.OP_RETURN, buffer]);
|
||||
}
|
||||
exports.encode = encode;
|
||||
function decode(buffer) {
|
||||
typeforce(check, buffer);
|
||||
return bscript.decompile(buffer)[1].slice(4, 36);
|
||||
}
|
||||
exports.decode = decode;
|
6
src/templates/witnesspubkeyhash/index.js
Normal file
6
src/templates/witnesspubkeyhash/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const input = require("./input");
|
||||
exports.input = input;
|
||||
const output = require("./output");
|
||||
exports.output = output;
|
15
src/templates/witnesspubkeyhash/input.js
Normal file
15
src/templates/witnesspubkeyhash/input.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
"use strict";
|
||||
// {signature} {pubKey}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
function isCompressedCanonicalPubKey(pubKey) {
|
||||
return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33;
|
||||
}
|
||||
function check(script) {
|
||||
const chunks = bscript.decompile(script);
|
||||
return chunks.length === 2 &&
|
||||
bscript.isCanonicalScriptSignature(chunks[0]) &&
|
||||
isCompressedCanonicalPubKey(chunks[1]);
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'witnessPubKeyHash input'; };
|
13
src/templates/witnesspubkeyhash/output.js
Normal file
13
src/templates/witnesspubkeyhash/output.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
"use strict";
|
||||
// OP_0 {pubKeyHash}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const script_1 = require("../../script");
|
||||
function check(script) {
|
||||
const buffer = bscript.compile(script);
|
||||
return buffer.length === 22 &&
|
||||
buffer[0] === script_1.OPS.OP_0 &&
|
||||
buffer[1] === 0x14;
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'Witness pubKeyHash output'; };
|
6
src/templates/witnessscripthash/index.js
Normal file
6
src/templates/witnessscripthash/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const input = require("./input");
|
||||
exports.input = input;
|
||||
const output = require("./output");
|
||||
exports.output = output;
|
34
src/templates/witnessscripthash/input.js
Normal file
34
src/templates/witnessscripthash/input.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
"use strict";
|
||||
// <scriptSig> {serialized scriptPubKey script}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const typeforce = require('typeforce');
|
||||
const p2ms = require("../multisig");
|
||||
const p2pk = require("../pubkey");
|
||||
const p2pkh = require("../pubkeyhash");
|
||||
function check(chunks, allowIncomplete) {
|
||||
typeforce(typeforce.Array, chunks);
|
||||
if (chunks.length < 1)
|
||||
return false;
|
||||
const witnessScript = chunks[chunks.length - 1];
|
||||
if (!Buffer.isBuffer(witnessScript))
|
||||
return false;
|
||||
const witnessScriptChunks = bscript.decompile(witnessScript);
|
||||
// is witnessScript a valid script?
|
||||
if (!witnessScriptChunks || witnessScriptChunks.length === 0)
|
||||
return false;
|
||||
const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1));
|
||||
// match types
|
||||
if (p2pkh.input.check(witnessRawScriptSig) &&
|
||||
p2pkh.output.check(witnessScriptChunks))
|
||||
return true;
|
||||
if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) &&
|
||||
p2ms.output.check(witnessScriptChunks))
|
||||
return true;
|
||||
if (p2pk.input.check(witnessRawScriptSig) &&
|
||||
p2pk.output.check(witnessScriptChunks))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'witnessScriptHash input'; };
|
13
src/templates/witnessscripthash/output.js
Normal file
13
src/templates/witnessscripthash/output.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
"use strict";
|
||||
// OP_0 {scriptHash}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bscript = require("../../script");
|
||||
const script_1 = require("../../script");
|
||||
function check(script) {
|
||||
const buffer = bscript.compile(script);
|
||||
return buffer.length === 34 &&
|
||||
buffer[0] === script_1.OPS.OP_0 &&
|
||||
buffer[1] === 0x20;
|
||||
}
|
||||
exports.check = check;
|
||||
check.toJSON = function () { return 'Witness scriptHash output'; };
|
448
src/transaction.js
Normal file
448
src/transaction.js
Normal file
|
@ -0,0 +1,448 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const bcrypto = require("./crypto");
|
||||
const bscript = require("./script");
|
||||
const types = require("./types");
|
||||
const bufferutils = require("./bufferutils");
|
||||
const bufferutils_1 = require("./bufferutils");
|
||||
const script_1 = require("./script");
|
||||
const typeforce = require('typeforce');
|
||||
const varuint = require('varuint-bitcoin');
|
||||
function varSliceSize(someScript) {
|
||||
const length = someScript.length;
|
||||
return varuint.encodingLength(length) + length;
|
||||
}
|
||||
function vectorSize(someVector) {
|
||||
const length = someVector.length;
|
||||
return varuint.encodingLength(length) + someVector.reduce((sum, witness) => {
|
||||
return sum + varSliceSize(witness);
|
||||
}, 0);
|
||||
}
|
||||
const EMPTY_SCRIPT = Buffer.allocUnsafe(0);
|
||||
const EMPTY_WITNESS = [];
|
||||
const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex');
|
||||
const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex');
|
||||
const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex');
|
||||
const BLANK_OUTPUT = {
|
||||
script: EMPTY_SCRIPT,
|
||||
valueBuffer: VALUE_UINT64_MAX
|
||||
};
|
||||
function isOutput(out) {
|
||||
return out.value !== undefined;
|
||||
}
|
||||
class Transaction {
|
||||
constructor() {
|
||||
this.version = 1;
|
||||
this.locktime = 0;
|
||||
this.ins = [];
|
||||
this.outs = [];
|
||||
}
|
||||
static fromBuffer(buffer, __noStrict) {
|
||||
let offset = 0;
|
||||
function readSlice(n) {
|
||||
offset += n;
|
||||
return buffer.slice(offset - n, offset);
|
||||
}
|
||||
function readUInt32() {
|
||||
const i = buffer.readUInt32LE(offset);
|
||||
offset += 4;
|
||||
return i;
|
||||
}
|
||||
function readInt32() {
|
||||
const i = buffer.readInt32LE(offset);
|
||||
offset += 4;
|
||||
return i;
|
||||
}
|
||||
function readUInt64() {
|
||||
const i = bufferutils.readUInt64LE(buffer, offset);
|
||||
offset += 8;
|
||||
return i;
|
||||
}
|
||||
function readVarInt() {
|
||||
const vi = varuint.decode(buffer, offset);
|
||||
offset += varuint.decode.bytes;
|
||||
return vi;
|
||||
}
|
||||
function readVarSlice() {
|
||||
return readSlice(readVarInt());
|
||||
}
|
||||
function readVector() {
|
||||
const count = readVarInt();
|
||||
const vector = [];
|
||||
for (var i = 0; i < count; i++)
|
||||
vector.push(readVarSlice());
|
||||
return vector;
|
||||
}
|
||||
const tx = new Transaction();
|
||||
tx.version = readInt32();
|
||||
const marker = buffer.readUInt8(offset);
|
||||
const flag = buffer.readUInt8(offset + 1);
|
||||
let hasWitnesses = false;
|
||||
if (marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
|
||||
flag === Transaction.ADVANCED_TRANSACTION_FLAG) {
|
||||
offset += 2;
|
||||
hasWitnesses = true;
|
||||
}
|
||||
const vinLen = readVarInt();
|
||||
for (var i = 0; i < vinLen; ++i) {
|
||||
tx.ins.push({
|
||||
hash: readSlice(32),
|
||||
index: readUInt32(),
|
||||
script: readVarSlice(),
|
||||
sequence: readUInt32(),
|
||||
witness: EMPTY_WITNESS
|
||||
});
|
||||
}
|
||||
const voutLen = readVarInt();
|
||||
for (i = 0; i < voutLen; ++i) {
|
||||
tx.outs.push({
|
||||
value: readUInt64(),
|
||||
script: readVarSlice()
|
||||
});
|
||||
}
|
||||
if (hasWitnesses) {
|
||||
for (i = 0; i < vinLen; ++i) {
|
||||
tx.ins[i].witness = readVector();
|
||||
}
|
||||
// was this pointless?
|
||||
if (!tx.hasWitnesses())
|
||||
throw new Error('Transaction has superfluous witness data');
|
||||
}
|
||||
tx.locktime = readUInt32();
|
||||
if (__noStrict)
|
||||
return tx;
|
||||
if (offset !== buffer.length)
|
||||
throw new Error('Transaction has unexpected data');
|
||||
return tx;
|
||||
}
|
||||
static fromHex(hex) {
|
||||
return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false);
|
||||
}
|
||||
static isCoinbaseHash(buffer) {
|
||||
typeforce(types.Hash256bit, buffer);
|
||||
for (var i = 0; i < 32; ++i) {
|
||||
if (buffer[i] !== 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
isCoinbase() {
|
||||
return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash);
|
||||
}
|
||||
addInput(hash, index, sequence, scriptSig) {
|
||||
typeforce(types.tuple(types.Hash256bit, types.UInt32, types.maybe(types.UInt32), types.maybe(types.Buffer)), arguments);
|
||||
if (types.Null(sequence)) {
|
||||
sequence = Transaction.DEFAULT_SEQUENCE;
|
||||
}
|
||||
// Add the input and return the input's index
|
||||
return (this.ins.push({
|
||||
hash: hash,
|
||||
index: index,
|
||||
script: scriptSig || EMPTY_SCRIPT,
|
||||
sequence: sequence,
|
||||
witness: EMPTY_WITNESS
|
||||
}) - 1);
|
||||
}
|
||||
addOutput(scriptPubKey, value) {
|
||||
typeforce(types.tuple(types.Buffer, types.Satoshi), arguments);
|
||||
// Add the output and return the output's index
|
||||
return (this.outs.push({
|
||||
script: scriptPubKey,
|
||||
value: value
|
||||
}) - 1);
|
||||
}
|
||||
hasWitnesses() {
|
||||
return this.ins.some((x) => {
|
||||
return x.witness.length !== 0;
|
||||
});
|
||||
}
|
||||
weight() {
|
||||
const base = this.__byteLength(false);
|
||||
const total = this.__byteLength(true);
|
||||
return base * 3 + total;
|
||||
}
|
||||
virtualSize() {
|
||||
return Math.ceil(this.weight() / 4);
|
||||
}
|
||||
byteLength() {
|
||||
return this.__byteLength(true);
|
||||
}
|
||||
__byteLength(__allowWitness) {
|
||||
const hasWitnesses = __allowWitness && this.hasWitnesses();
|
||||
return ((hasWitnesses ? 10 : 8) +
|
||||
varuint.encodingLength(this.ins.length) +
|
||||
varuint.encodingLength(this.outs.length) +
|
||||
this.ins.reduce((sum, input) => {
|
||||
return sum + 40 + varSliceSize(input.script);
|
||||
}, 0) +
|
||||
this.outs.reduce((sum, output) => {
|
||||
return sum + 8 + varSliceSize(output.script);
|
||||
}, 0) +
|
||||
(hasWitnesses ? this.ins.reduce((sum, input) => {
|
||||
return sum + vectorSize(input.witness);
|
||||
}, 0) : 0));
|
||||
}
|
||||
clone() {
|
||||
const newTx = new Transaction();
|
||||
newTx.version = this.version;
|
||||
newTx.locktime = this.locktime;
|
||||
newTx.ins = this.ins.map((txIn) => {
|
||||
return {
|
||||
hash: txIn.hash,
|
||||
index: txIn.index,
|
||||
script: txIn.script,
|
||||
sequence: txIn.sequence,
|
||||
witness: txIn.witness
|
||||
};
|
||||
});
|
||||
newTx.outs = this.outs.map((txOut) => {
|
||||
return {
|
||||
script: txOut.script,
|
||||
value: txOut.value
|
||||
};
|
||||
});
|
||||
return newTx;
|
||||
}
|
||||
/**
|
||||
* Hash transaction for signing a specific input.
|
||||
*
|
||||
* Bitcoin uses a different hash for each signed transaction input.
|
||||
* This method copies the transaction, makes the necessary changes based on the
|
||||
* hashType, and then hashes the result.
|
||||
* This hash can then be used to sign the provided transaction input.
|
||||
*/
|
||||
hashForSignature(inIndex, prevOutScript, hashType) {
|
||||
typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments);
|
||||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29
|
||||
if (inIndex >= this.ins.length)
|
||||
return ONE;
|
||||
// ignore OP_CODESEPARATOR
|
||||
const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter((x) => {
|
||||
return x !== script_1.OPS.OP_CODESEPARATOR;
|
||||
}));
|
||||
const txTmp = this.clone();
|
||||
// SIGHASH_NONE: ignore all outputs? (wildcard payee)
|
||||
if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) {
|
||||
txTmp.outs = [];
|
||||
// ignore sequence numbers (except at inIndex)
|
||||
txTmp.ins.forEach((input, i) => {
|
||||
if (i === inIndex)
|
||||
return;
|
||||
input.sequence = 0;
|
||||
});
|
||||
// SIGHASH_SINGLE: ignore all outputs, except at the same index?
|
||||
}
|
||||
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) {
|
||||
// https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60
|
||||
if (inIndex >= this.outs.length)
|
||||
return ONE;
|
||||
// truncate outputs after
|
||||
txTmp.outs.length = inIndex + 1;
|
||||
// "blank" outputs before
|
||||
for (var i = 0; i < inIndex; i++) {
|
||||
txTmp.outs[i] = BLANK_OUTPUT;
|
||||
}
|
||||
// ignore sequence numbers (except at inIndex)
|
||||
txTmp.ins.forEach((input, y) => {
|
||||
if (y === inIndex)
|
||||
return;
|
||||
input.sequence = 0;
|
||||
});
|
||||
}
|
||||
// SIGHASH_ANYONECANPAY: ignore inputs entirely?
|
||||
if (hashType & Transaction.SIGHASH_ANYONECANPAY) {
|
||||
txTmp.ins = [txTmp.ins[inIndex]];
|
||||
txTmp.ins[0].script = ourScript;
|
||||
// SIGHASH_ALL: only ignore input scripts
|
||||
}
|
||||
else {
|
||||
// "blank" others input scripts
|
||||
txTmp.ins.forEach((input) => {
|
||||
input.script = EMPTY_SCRIPT;
|
||||
});
|
||||
txTmp.ins[inIndex].script = ourScript;
|
||||
}
|
||||
// serialize and hash
|
||||
const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4);
|
||||
buffer.writeInt32LE(hashType, buffer.length - 4);
|
||||
txTmp.__toBuffer(buffer, 0, false);
|
||||
return bcrypto.hash256(buffer);
|
||||
}
|
||||
hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
|
||||
typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments);
|
||||
let tbuffer = Buffer.from([]);
|
||||
let toffset = 0;
|
||||
function writeSlice(slice) {
|
||||
toffset += slice.copy(tbuffer, toffset);
|
||||
}
|
||||
function writeUInt32(i) {
|
||||
toffset = tbuffer.writeUInt32LE(i, toffset);
|
||||
}
|
||||
function writeUInt64(i) {
|
||||
toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset);
|
||||
}
|
||||
function writeVarInt(i) {
|
||||
varuint.encode(i, tbuffer, toffset);
|
||||
toffset += varuint.encode.bytes;
|
||||
}
|
||||
function writeVarSlice(slice) {
|
||||
writeVarInt(slice.length);
|
||||
writeSlice(slice);
|
||||
}
|
||||
let hashOutputs = ZERO;
|
||||
let hashPrevouts = ZERO;
|
||||
let hashSequence = ZERO;
|
||||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) {
|
||||
tbuffer = Buffer.allocUnsafe(36 * this.ins.length);
|
||||
toffset = 0;
|
||||
this.ins.forEach((txIn) => {
|
||||
writeSlice(txIn.hash);
|
||||
writeUInt32(txIn.index);
|
||||
});
|
||||
hashPrevouts = bcrypto.hash256(tbuffer);
|
||||
}
|
||||
if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) &&
|
||||
(hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
||||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
|
||||
tbuffer = Buffer.allocUnsafe(4 * this.ins.length);
|
||||
toffset = 0;
|
||||
this.ins.forEach((txIn) => {
|
||||
writeUInt32(txIn.sequence);
|
||||
});
|
||||
hashSequence = bcrypto.hash256(tbuffer);
|
||||
}
|
||||
if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE &&
|
||||
(hashType & 0x1f) !== Transaction.SIGHASH_NONE) {
|
||||
const txOutsSize = this.outs.reduce((sum, output) => {
|
||||
return sum + 8 + varSliceSize(output.script);
|
||||
}, 0);
|
||||
tbuffer = Buffer.allocUnsafe(txOutsSize);
|
||||
toffset = 0;
|
||||
this.outs.forEach((out) => {
|
||||
writeUInt64(out.value);
|
||||
writeVarSlice(out.script);
|
||||
});
|
||||
hashOutputs = bcrypto.hash256(tbuffer);
|
||||
}
|
||||
else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) {
|
||||
const output = this.outs[inIndex];
|
||||
tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script));
|
||||
toffset = 0;
|
||||
writeUInt64(output.value);
|
||||
writeVarSlice(output.script);
|
||||
hashOutputs = bcrypto.hash256(tbuffer);
|
||||
}
|
||||
tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript));
|
||||
toffset = 0;
|
||||
const input = this.ins[inIndex];
|
||||
writeUInt32(this.version);
|
||||
writeSlice(hashPrevouts);
|
||||
writeSlice(hashSequence);
|
||||
writeSlice(input.hash);
|
||||
writeUInt32(input.index);
|
||||
writeVarSlice(prevOutScript);
|
||||
writeUInt64(value);
|
||||
writeUInt32(input.sequence);
|
||||
writeSlice(hashOutputs);
|
||||
writeUInt32(this.locktime);
|
||||
writeUInt32(hashType);
|
||||
return bcrypto.hash256(tbuffer);
|
||||
}
|
||||
getHash(forWitness) {
|
||||
// wtxid for coinbase is always 32 bytes of 0x00
|
||||
if (forWitness && this.isCoinbase())
|
||||
return Buffer.alloc(32, 0);
|
||||
return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness));
|
||||
}
|
||||
getId() {
|
||||
// transaction hash's are displayed in reverse order
|
||||
return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex');
|
||||
}
|
||||
toBuffer(buffer, initialOffset) {
|
||||
return this.__toBuffer(buffer, initialOffset, true);
|
||||
}
|
||||
__toBuffer(buffer, initialOffset, __allowWitness) {
|
||||
if (!buffer)
|
||||
buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness));
|
||||
let offset = initialOffset || 0;
|
||||
function writeSlice(slice) {
|
||||
offset += slice.copy(buffer, offset);
|
||||
}
|
||||
function writeUInt8(i) {
|
||||
offset = buffer.writeUInt8(i, offset);
|
||||
}
|
||||
function writeUInt32(i) {
|
||||
offset = buffer.writeUInt32LE(i, offset);
|
||||
}
|
||||
function writeInt32(i) {
|
||||
offset = buffer.writeInt32LE(i, offset);
|
||||
}
|
||||
function writeUInt64(i) {
|
||||
offset = bufferutils.writeUInt64LE(buffer, i, offset);
|
||||
}
|
||||
function writeVarInt(i) {
|
||||
varuint.encode(i, buffer, offset);
|
||||
offset += varuint.encode.bytes;
|
||||
}
|
||||
function writeVarSlice(slice) {
|
||||
writeVarInt(slice.length);
|
||||
writeSlice(slice);
|
||||
}
|
||||
function writeVector(vector) {
|
||||
writeVarInt(vector.length);
|
||||
vector.forEach(writeVarSlice);
|
||||
}
|
||||
writeInt32(this.version);
|
||||
const hasWitnesses = __allowWitness && this.hasWitnesses();
|
||||
if (hasWitnesses) {
|
||||
writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER);
|
||||
writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG);
|
||||
}
|
||||
writeVarInt(this.ins.length);
|
||||
this.ins.forEach((txIn) => {
|
||||
writeSlice(txIn.hash);
|
||||
writeUInt32(txIn.index);
|
||||
writeVarSlice(txIn.script);
|
||||
writeUInt32(txIn.sequence);
|
||||
});
|
||||
writeVarInt(this.outs.length);
|
||||
this.outs.forEach((txOut) => {
|
||||
if (isOutput(txOut)) {
|
||||
writeUInt64(txOut.value);
|
||||
}
|
||||
else {
|
||||
writeSlice(txOut.valueBuffer);
|
||||
}
|
||||
writeVarSlice(txOut.script);
|
||||
});
|
||||
if (hasWitnesses) {
|
||||
this.ins.forEach((input) => {
|
||||
writeVector(input.witness);
|
||||
});
|
||||
}
|
||||
writeUInt32(this.locktime);
|
||||
// avoid slicing unless necessary
|
||||
if (initialOffset !== undefined)
|
||||
return buffer.slice(initialOffset, offset);
|
||||
return buffer;
|
||||
}
|
||||
toHex() {
|
||||
return this.toBuffer(undefined, undefined).toString('hex');
|
||||
}
|
||||
setInputScript(index, scriptSig) {
|
||||
typeforce(types.tuple(types.Number, types.Buffer), arguments);
|
||||
this.ins[index].script = scriptSig;
|
||||
}
|
||||
setWitness(index, witness) {
|
||||
typeforce(types.tuple(types.Number, [types.Buffer]), arguments);
|
||||
this.ins[index].witness = witness;
|
||||
}
|
||||
}
|
||||
Transaction.DEFAULT_SEQUENCE = 0xffffffff;
|
||||
Transaction.SIGHASH_ALL = 0x01;
|
||||
Transaction.SIGHASH_NONE = 0x02;
|
||||
Transaction.SIGHASH_SINGLE = 0x03;
|
||||
Transaction.SIGHASH_ANYONECANPAY = 0x80;
|
||||
Transaction.ADVANCED_TRANSACTION_MARKER = 0x00;
|
||||
Transaction.ADVANCED_TRANSACTION_FLAG = 0x01;
|
||||
exports.Transaction = Transaction;
|
699
src/transaction_builder.js
Normal file
699
src/transaction_builder.js
Normal file
|
@ -0,0 +1,699 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const networks = require("./networks");
|
||||
const bufferutils_1 = require("./bufferutils");
|
||||
const transaction_1 = require("./transaction");
|
||||
const ECPair = require("./ecpair");
|
||||
const types = require("./types");
|
||||
const baddress = require("./address");
|
||||
const bcrypto = require("./crypto");
|
||||
const bscript = require("./script");
|
||||
const payments = require("./payments");
|
||||
const classify = require("./classify");
|
||||
const script_1 = require("./script");
|
||||
const typeforce = require('typeforce');
|
||||
const SCRIPT_TYPES = classify.types;
|
||||
function txIsString(tx) {
|
||||
return typeof tx === 'string' || tx instanceof String;
|
||||
}
|
||||
function txIsTransaction(tx) {
|
||||
return tx instanceof transaction_1.Transaction;
|
||||
}
|
||||
class TransactionBuilder {
|
||||
constructor(network, maximumFeeRate) {
|
||||
this.__prevTxSet = {};
|
||||
this.network = network || networks.bitcoin;
|
||||
// WARNING: This is __NOT__ to be relied on, its just another potential safety mechanism (safety in-depth)
|
||||
this.maximumFeeRate = maximumFeeRate || 2500;
|
||||
this.__inputs = [];
|
||||
this.__tx = new transaction_1.Transaction();
|
||||
this.__tx.version = 2;
|
||||
}
|
||||
static fromTransaction(transaction, network) {
|
||||
const txb = new TransactionBuilder(network);
|
||||
// Copy transaction fields
|
||||
txb.setVersion(transaction.version);
|
||||
txb.setLockTime(transaction.locktime);
|
||||
// Copy outputs (done first to avoid signature invalidation)
|
||||
transaction.outs.forEach(txOut => {
|
||||
txb.addOutput(txOut.script, txOut.value);
|
||||
});
|
||||
// Copy inputs
|
||||
transaction.ins.forEach(txIn => {
|
||||
txb.__addInputUnsafe(txIn.hash, txIn.index, {
|
||||
sequence: txIn.sequence,
|
||||
script: txIn.script,
|
||||
witness: txIn.witness
|
||||
});
|
||||
});
|
||||
// fix some things not possible through the public API
|
||||
txb.__inputs.forEach((input, i) => {
|
||||
fixMultisigOrder(input, transaction, i);
|
||||
});
|
||||
return txb;
|
||||
}
|
||||
setLockTime(locktime) {
|
||||
typeforce(types.UInt32, locktime);
|
||||
// if any signatures exist, throw
|
||||
if (this.__inputs.some(input => {
|
||||
if (!input.signatures)
|
||||
return false;
|
||||
return input.signatures.some(s => s !== undefined);
|
||||
})) {
|
||||
throw new Error('No, this would invalidate signatures');
|
||||
}
|
||||
this.__tx.locktime = locktime;
|
||||
}
|
||||
setVersion(version) {
|
||||
typeforce(types.UInt32, version);
|
||||
// XXX: this might eventually become more complex depending on what the versions represent
|
||||
this.__tx.version = version;
|
||||
}
|
||||
addInput(txHash, vout, sequence, prevOutScript) {
|
||||
if (!this.__canModifyInputs()) {
|
||||
throw new Error('No, this would invalidate signatures');
|
||||
}
|
||||
let value = undefined;
|
||||
// is it a hex string?
|
||||
if (txIsString(txHash)) {
|
||||
// transaction hashs's are displayed in reverse order, un-reverse it
|
||||
txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex'));
|
||||
// is it a Transaction object?
|
||||
}
|
||||
else if (txIsTransaction(txHash)) {
|
||||
const txOut = txHash.outs[vout];
|
||||
prevOutScript = txOut.script;
|
||||
value = txOut.value;
|
||||
txHash = txHash.getHash(false);
|
||||
}
|
||||
return this.__addInputUnsafe(txHash, vout, {
|
||||
sequence: sequence,
|
||||
prevOutScript: prevOutScript,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
__addInputUnsafe(txHash, vout, options) {
|
||||
if (transaction_1.Transaction.isCoinbaseHash(txHash)) {
|
||||
throw new Error('coinbase inputs not supported');
|
||||
}
|
||||
const prevTxOut = txHash.toString('hex') + ':' + vout;
|
||||
if (this.__prevTxSet[prevTxOut] !== undefined)
|
||||
throw new Error('Duplicate TxOut: ' + prevTxOut);
|
||||
let input = {};
|
||||
// derive what we can from the scriptSig
|
||||
if (options.script !== undefined) {
|
||||
input = expandInput(options.script, options.witness || []);
|
||||
}
|
||||
// if an input value was given, retain it
|
||||
if (options.value !== undefined) {
|
||||
input.value = options.value;
|
||||
}
|
||||
// derive what we can from the previous transactions output script
|
||||
if (!input.prevOutScript && options.prevOutScript) {
|
||||
let prevOutType;
|
||||
if (!input.pubkeys && !input.signatures) {
|
||||
const expanded = expandOutput(options.prevOutScript);
|
||||
if (expanded.pubkeys) {
|
||||
input.pubkeys = expanded.pubkeys;
|
||||
input.signatures = expanded.signatures;
|
||||
}
|
||||
prevOutType = expanded.type;
|
||||
}
|
||||
input.prevOutScript = options.prevOutScript;
|
||||
input.prevOutType = prevOutType || classify.output(options.prevOutScript);
|
||||
}
|
||||
const vin = this.__tx.addInput(txHash, vout, options.sequence, options.scriptSig);
|
||||
this.__inputs[vin] = input;
|
||||
this.__prevTxSet[prevTxOut] = true;
|
||||
return vin;
|
||||
}
|
||||
addOutput(scriptPubKey, value) {
|
||||
if (!this.__canModifyOutputs()) {
|
||||
throw new Error('No, this would invalidate signatures');
|
||||
}
|
||||
// Attempt to get a script if it's a base58 or bech32 address string
|
||||
if (typeof scriptPubKey === 'string') {
|
||||
scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network);
|
||||
}
|
||||
return this.__tx.addOutput(scriptPubKey, value);
|
||||
}
|
||||
build() {
|
||||
return this.__build(false);
|
||||
}
|
||||
buildIncomplete() {
|
||||
return this.__build(true);
|
||||
}
|
||||
__build(allowIncomplete) {
|
||||
if (!allowIncomplete) {
|
||||
if (!this.__tx.ins.length)
|
||||
throw new Error('Transaction has no inputs');
|
||||
if (!this.__tx.outs.length)
|
||||
throw new Error('Transaction has no outputs');
|
||||
}
|
||||
const tx = this.__tx.clone();
|
||||
// create script signatures from inputs
|
||||
this.__inputs.forEach((input, i) => {
|
||||
if (!input.prevOutType && !allowIncomplete)
|
||||
throw new Error('Transaction is not complete');
|
||||
const result = build(input.prevOutType, input, allowIncomplete);
|
||||
if (!result) {
|
||||
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD)
|
||||
throw new Error('Unknown input type');
|
||||
if (!allowIncomplete)
|
||||
throw new Error('Not enough information');
|
||||
return;
|
||||
}
|
||||
tx.setInputScript(i, result.input);
|
||||
tx.setWitness(i, result.witness);
|
||||
});
|
||||
if (!allowIncomplete) {
|
||||
// do not rely on this, its merely a last resort
|
||||
if (this.__overMaximumFees(tx.virtualSize())) {
|
||||
throw new Error('Transaction has absurd fees');
|
||||
}
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
sign(vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) {
|
||||
// TODO: remove keyPair.network matching in 4.0.0
|
||||
if (keyPair.network && keyPair.network !== this.network)
|
||||
throw new TypeError('Inconsistent network');
|
||||
if (!this.__inputs[vin])
|
||||
throw new Error('No input at index: ' + vin);
|
||||
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL;
|
||||
if (this.__needsOutputs(hashType))
|
||||
throw new Error('Transaction needs outputs');
|
||||
const input = this.__inputs[vin];
|
||||
// if redeemScript was previously provided, enforce consistency
|
||||
if (input.redeemScript !== undefined &&
|
||||
redeemScript &&
|
||||
!input.redeemScript.equals(redeemScript)) {
|
||||
throw new Error('Inconsistent redeemScript');
|
||||
}
|
||||
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey();
|
||||
if (!canSign(input)) {
|
||||
if (witnessValue !== undefined) {
|
||||
if (input.value !== undefined && input.value !== witnessValue)
|
||||
throw new Error('Input didn\'t match witnessValue');
|
||||
typeforce(types.Satoshi, witnessValue);
|
||||
input.value = witnessValue;
|
||||
}
|
||||
if (!canSign(input)) {
|
||||
const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript);
|
||||
// updates inline
|
||||
Object.assign(input, prepared);
|
||||
}
|
||||
if (!canSign(input))
|
||||
throw Error(input.prevOutType + ' not supported');
|
||||
}
|
||||
// ready to sign
|
||||
let signatureHash;
|
||||
if (input.hasWitness) {
|
||||
signatureHash = this.__tx.hashForWitnessV0(vin, input.signScript, input.value, hashType);
|
||||
}
|
||||
else {
|
||||
signatureHash = this.__tx.hashForSignature(vin, input.signScript, hashType);
|
||||
}
|
||||
// enforce in order signing of public keys
|
||||
const signed = input.pubkeys.some((pubKey, i) => {
|
||||
if (!ourPubKey.equals(pubKey))
|
||||
return false;
|
||||
if (input.signatures[i])
|
||||
throw new Error('Signature already exists');
|
||||
// TODO: add tests
|
||||
if (ourPubKey.length !== 33 && input.hasWitness) {
|
||||
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH');
|
||||
}
|
||||
const signature = keyPair.sign(signatureHash);
|
||||
input.signatures[i] = bscript.signature.encode(signature, hashType);
|
||||
return true;
|
||||
});
|
||||
if (!signed)
|
||||
throw new Error('Key pair cannot sign for this input');
|
||||
}
|
||||
__canModifyInputs() {
|
||||
return this.__inputs.every(input => {
|
||||
if (!input.signatures)
|
||||
return true;
|
||||
return input.signatures.every(signature => {
|
||||
if (!signature)
|
||||
return true;
|
||||
const hashType = signatureHashType(signature);
|
||||
// if SIGHASH_ANYONECANPAY is set, signatures would not
|
||||
// be invalidated by more inputs
|
||||
return (hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
__needsOutputs(signingHashType) {
|
||||
if (signingHashType === transaction_1.Transaction.SIGHASH_ALL) {
|
||||
return this.__tx.outs.length === 0;
|
||||
}
|
||||
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs
|
||||
// .build() will fail, but .buildIncomplete() is OK
|
||||
return (this.__tx.outs.length === 0) && this.__inputs.some((input) => {
|
||||
if (!input.signatures)
|
||||
return false;
|
||||
return input.signatures.some((signature) => {
|
||||
if (!signature)
|
||||
return false; // no signature, no issue
|
||||
const hashType = signatureHashType(signature);
|
||||
if (hashType & transaction_1.Transaction.SIGHASH_NONE)
|
||||
return false; // SIGHASH_NONE doesn't care about outputs
|
||||
return true; // SIGHASH_* does care
|
||||
});
|
||||
});
|
||||
}
|
||||
__canModifyOutputs() {
|
||||
const nInputs = this.__tx.ins.length;
|
||||
const nOutputs = this.__tx.outs.length;
|
||||
return this.__inputs.every(input => {
|
||||
if (input.signatures === undefined)
|
||||
return true;
|
||||
return input.signatures.every((signature => {
|
||||
if (!signature)
|
||||
return true;
|
||||
const hashType = signatureHashType(signature);
|
||||
const hashTypeMod = hashType & 0x1f;
|
||||
if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE)
|
||||
return true;
|
||||
if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) {
|
||||
// if SIGHASH_SINGLE is set, and nInputs > nOutputs
|
||||
// some signatures would be invalidated by the addition
|
||||
// of more outputs
|
||||
return nInputs <= nOutputs;
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
__overMaximumFees(bytes) {
|
||||
// not all inputs will have .value defined
|
||||
const incoming = this.__inputs.reduce((a, x) => a + (x.value >>> 0), 0);
|
||||
// but all outputs do, and if we have any input value
|
||||
// we can immediately determine if the outputs are too small
|
||||
const outgoing = this.__tx.outs.reduce((a, x) => a + x.value, 0);
|
||||
const fee = incoming - outgoing;
|
||||
const feeRate = fee / bytes;
|
||||
return feeRate > this.maximumFeeRate;
|
||||
}
|
||||
}
|
||||
exports.TransactionBuilder = TransactionBuilder;
|
||||
function expandInput(scriptSig, witnessStack, type, scriptPubKey) {
|
||||
if (scriptSig.length === 0 && witnessStack.length === 0)
|
||||
return {};
|
||||
if (!type) {
|
||||
let ssType = classify.input(scriptSig, true);
|
||||
let wsType = classify.witness(witnessStack, true);
|
||||
if (ssType === SCRIPT_TYPES.NONSTANDARD)
|
||||
ssType = undefined;
|
||||
if (wsType === SCRIPT_TYPES.NONSTANDARD)
|
||||
wsType = undefined;
|
||||
type = ssType || wsType;
|
||||
}
|
||||
switch (type) {
|
||||
case SCRIPT_TYPES.P2WPKH: {
|
||||
const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack });
|
||||
return {
|
||||
prevOutScript: output,
|
||||
prevOutType: SCRIPT_TYPES.P2WPKH,
|
||||
pubkeys: [pubkey],
|
||||
signatures: [signature]
|
||||
};
|
||||
}
|
||||
case SCRIPT_TYPES.P2PKH: {
|
||||
const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig });
|
||||
return {
|
||||
prevOutScript: output,
|
||||
prevOutType: SCRIPT_TYPES.P2PKH,
|
||||
pubkeys: [pubkey],
|
||||
signatures: [signature]
|
||||
};
|
||||
}
|
||||
case SCRIPT_TYPES.P2PK: {
|
||||
const { signature } = payments.p2pk({ input: scriptSig });
|
||||
return {
|
||||
prevOutType: SCRIPT_TYPES.P2PK,
|
||||
pubkeys: [undefined],
|
||||
signatures: [signature]
|
||||
};
|
||||
}
|
||||
case SCRIPT_TYPES.P2MS: {
|
||||
const { m, pubkeys, signatures } = payments.p2ms({
|
||||
input: scriptSig,
|
||||
output: scriptPubKey
|
||||
}, { allowIncomplete: true });
|
||||
return {
|
||||
prevOutType: SCRIPT_TYPES.P2MS,
|
||||
pubkeys: pubkeys,
|
||||
signatures: signatures,
|
||||
maxSignatures: m
|
||||
};
|
||||
}
|
||||
}
|
||||
if (type === SCRIPT_TYPES.P2SH) {
|
||||
const { output, redeem } = payments.p2sh({
|
||||
input: scriptSig,
|
||||
witness: witnessStack
|
||||
});
|
||||
const outputType = classify.output(redeem.output);
|
||||
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output);
|
||||
if (!expanded.prevOutType)
|
||||
return {};
|
||||
return {
|
||||
prevOutScript: output,
|
||||
prevOutType: SCRIPT_TYPES.P2SH,
|
||||
redeemScript: redeem.output,
|
||||
redeemScriptType: expanded.prevOutType,
|
||||
witnessScript: expanded.witnessScript,
|
||||
witnessScriptType: expanded.witnessScriptType,
|
||||
pubkeys: expanded.pubkeys,
|
||||
signatures: expanded.signatures
|
||||
};
|
||||
}
|
||||
if (type === SCRIPT_TYPES.P2WSH) {
|
||||
const { output, redeem } = payments.p2wsh({
|
||||
input: scriptSig,
|
||||
witness: witnessStack
|
||||
});
|
||||
const outputType = classify.output(redeem.output);
|
||||
let expanded;
|
||||
if (outputType === SCRIPT_TYPES.P2WPKH) {
|
||||
expanded = expandInput(redeem.input, redeem.witness, outputType);
|
||||
}
|
||||
else {
|
||||
expanded = expandInput(bscript.compile(redeem.witness), [], outputType, redeem.output);
|
||||
}
|
||||
if (!expanded.prevOutType)
|
||||
return {};
|
||||
return {
|
||||
prevOutScript: output,
|
||||
prevOutType: SCRIPT_TYPES.P2WSH,
|
||||
witnessScript: redeem.output,
|
||||
witnessScriptType: expanded.prevOutType,
|
||||
pubkeys: expanded.pubkeys,
|
||||
signatures: expanded.signatures
|
||||
};
|
||||
}
|
||||
return {
|
||||
prevOutType: SCRIPT_TYPES.NONSTANDARD,
|
||||
prevOutScript: scriptSig
|
||||
};
|
||||
}
|
||||
// could be done in expandInput, but requires the original Transaction for hashForSignature
|
||||
function fixMultisigOrder(input, transaction, vin) {
|
||||
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript)
|
||||
return;
|
||||
if (input.pubkeys.length === input.signatures.length)
|
||||
return;
|
||||
const unmatched = input.signatures.concat();
|
||||
input.signatures = input.pubkeys.map(pubKey => {
|
||||
const keyPair = ECPair.fromPublicKey(pubKey);
|
||||
let match;
|
||||
// check for a signature
|
||||
unmatched.some((signature, i) => {
|
||||
// skip if undefined || OP_0
|
||||
if (!signature)
|
||||
return false;
|
||||
// TODO: avoid O(n) hashForSignature
|
||||
const parsed = bscript.signature.decode(signature);
|
||||
const hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType);
|
||||
// skip if signature does not match pubKey
|
||||
if (!keyPair.verify(hash, parsed.signature))
|
||||
return false;
|
||||
// remove matched signature from unmatched
|
||||
unmatched[i] = undefined;
|
||||
match = signature;
|
||||
return true;
|
||||
});
|
||||
return match;
|
||||
});
|
||||
}
|
||||
function expandOutput(script, ourPubKey) {
|
||||
typeforce(types.Buffer, script);
|
||||
const type = classify.output(script);
|
||||
switch (type) {
|
||||
case SCRIPT_TYPES.P2PKH: {
|
||||
if (!ourPubKey)
|
||||
return { type };
|
||||
// does our hash160(pubKey) match the output scripts?
|
||||
const pkh1 = payments.p2pkh({ output: script }).hash;
|
||||
const pkh2 = bcrypto.hash160(ourPubKey);
|
||||
if (!pkh1.equals(pkh2))
|
||||
return { type };
|
||||
return {
|
||||
type,
|
||||
pubkeys: [ourPubKey],
|
||||
signatures: [undefined]
|
||||
};
|
||||
}
|
||||
case SCRIPT_TYPES.P2WPKH: {
|
||||
if (!ourPubKey)
|
||||
return { type };
|
||||
// does our hash160(pubKey) match the output scripts?
|
||||
const wpkh1 = payments.p2wpkh({ output: script }).hash;
|
||||
const wpkh2 = bcrypto.hash160(ourPubKey);
|
||||
if (!wpkh1.equals(wpkh2))
|
||||
return { type };
|
||||
return {
|
||||
type,
|
||||
pubkeys: [ourPubKey],
|
||||
signatures: [undefined]
|
||||
};
|
||||
}
|
||||
case SCRIPT_TYPES.P2PK: {
|
||||
const p2pk = payments.p2pk({ output: script });
|
||||
return {
|
||||
type,
|
||||
pubkeys: [p2pk.pubkey],
|
||||
signatures: [undefined]
|
||||
};
|
||||
}
|
||||
case SCRIPT_TYPES.P2MS: {
|
||||
const p2ms = payments.p2ms({ output: script });
|
||||
return {
|
||||
type,
|
||||
pubkeys: p2ms.pubkeys,
|
||||
signatures: p2ms.pubkeys.map(() => undefined),
|
||||
maxSignatures: p2ms.m
|
||||
};
|
||||
}
|
||||
}
|
||||
return { type };
|
||||
}
|
||||
function prepareInput(input, ourPubKey, redeemScript, witnessScript) {
|
||||
if (redeemScript && witnessScript) {
|
||||
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } });
|
||||
const p2wshAlt = payments.p2wsh({ output: redeemScript });
|
||||
const p2sh = payments.p2sh({ redeem: { output: redeemScript } });
|
||||
const p2shAlt = payments.p2sh({ redeem: p2wsh });
|
||||
// enforces P2SH(P2WSH(...))
|
||||
if (!p2wsh.hash.equals(p2wshAlt.hash))
|
||||
throw new Error('Witness script inconsistent with prevOutScript');
|
||||
if (!p2sh.hash.equals(p2shAlt.hash))
|
||||
throw new Error('Redeem script inconsistent with prevOutScript');
|
||||
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey);
|
||||
if (!expanded.pubkeys)
|
||||
throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')');
|
||||
if (input.signatures && input.signatures.some(x => x !== undefined)) {
|
||||
expanded.signatures = input.signatures;
|
||||
}
|
||||
let signScript = witnessScript;
|
||||
if (expanded.type === SCRIPT_TYPES.P2WPKH)
|
||||
throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure');
|
||||
return {
|
||||
redeemScript,
|
||||
redeemScriptType: SCRIPT_TYPES.P2WSH,
|
||||
witnessScript,
|
||||
witnessScriptType: expanded.type,
|
||||
prevOutType: SCRIPT_TYPES.P2SH,
|
||||
prevOutScript: p2sh.output,
|
||||
hasWitness: true,
|
||||
signScript,
|
||||
signType: expanded.type,
|
||||
pubkeys: expanded.pubkeys,
|
||||
signatures: expanded.signatures,
|
||||
maxSignatures: expanded.maxSignatures
|
||||
};
|
||||
}
|
||||
if (redeemScript) {
|
||||
const p2sh = payments.p2sh({ redeem: { output: redeemScript } });
|
||||
if (input.prevOutScript) {
|
||||
let p2shAlt;
|
||||
try {
|
||||
p2shAlt = payments.p2sh({ output: input.prevOutScript });
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error('PrevOutScript must be P2SH');
|
||||
}
|
||||
if (!p2sh.hash.equals(p2shAlt.hash))
|
||||
throw new Error('Redeem script inconsistent with prevOutScript');
|
||||
}
|
||||
const expanded = expandOutput(p2sh.redeem.output, ourPubKey);
|
||||
if (!expanded.pubkeys)
|
||||
throw new Error(expanded.type + ' not supported as redeemScript (' + bscript.toASM(redeemScript) + ')');
|
||||
if (input.signatures && input.signatures.some(x => x !== undefined)) {
|
||||
expanded.signatures = input.signatures;
|
||||
}
|
||||
let signScript = redeemScript;
|
||||
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
|
||||
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output;
|
||||
}
|
||||
return {
|
||||
redeemScript,
|
||||
redeemScriptType: expanded.type,
|
||||
prevOutType: SCRIPT_TYPES.P2SH,
|
||||
prevOutScript: p2sh.output,
|
||||
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH,
|
||||
signScript,
|
||||
signType: expanded.type,
|
||||
pubkeys: expanded.pubkeys,
|
||||
signatures: expanded.signatures,
|
||||
maxSignatures: expanded.maxSignatures
|
||||
};
|
||||
}
|
||||
if (witnessScript) {
|
||||
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } });
|
||||
if (input.prevOutScript) {
|
||||
const p2wshAlt = payments.p2wsh({ output: input.prevOutScript });
|
||||
if (!p2wsh.hash.equals(p2wshAlt.hash))
|
||||
throw new Error('Witness script inconsistent with prevOutScript');
|
||||
}
|
||||
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey);
|
||||
if (!expanded.pubkeys)
|
||||
throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')');
|
||||
if (input.signatures && input.signatures.some(x => x !== undefined)) {
|
||||
expanded.signatures = input.signatures;
|
||||
}
|
||||
let signScript = witnessScript;
|
||||
if (expanded.type === SCRIPT_TYPES.P2WPKH)
|
||||
throw new Error('P2WSH(P2WPKH) is a consensus failure');
|
||||
return {
|
||||
witnessScript,
|
||||
witnessScriptType: expanded.type,
|
||||
prevOutType: SCRIPT_TYPES.P2WSH,
|
||||
prevOutScript: p2wsh.output,
|
||||
hasWitness: true,
|
||||
signScript,
|
||||
signType: expanded.type,
|
||||
pubkeys: expanded.pubkeys,
|
||||
signatures: expanded.signatures,
|
||||
maxSignatures: expanded.maxSignatures
|
||||
};
|
||||
}
|
||||
if (input.prevOutType && input.prevOutScript) {
|
||||
// embedded scripts are not possible without extra information
|
||||
if (input.prevOutType === SCRIPT_TYPES.P2SH)
|
||||
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript');
|
||||
if (input.prevOutType === SCRIPT_TYPES.P2WSH)
|
||||
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript');
|
||||
if (!input.prevOutScript)
|
||||
throw new Error('PrevOutScript is missing');
|
||||
const expanded = expandOutput(input.prevOutScript, ourPubKey);
|
||||
if (!expanded.pubkeys)
|
||||
throw new Error(expanded.type + ' not supported (' + bscript.toASM(input.prevOutScript) + ')');
|
||||
if (input.signatures && input.signatures.some(x => x !== undefined)) {
|
||||
expanded.signatures = input.signatures;
|
||||
}
|
||||
let signScript = input.prevOutScript;
|
||||
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
|
||||
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output;
|
||||
}
|
||||
return {
|
||||
prevOutType: expanded.type,
|
||||
prevOutScript: input.prevOutScript,
|
||||
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH,
|
||||
signScript,
|
||||
signType: expanded.type,
|
||||
pubkeys: expanded.pubkeys,
|
||||
signatures: expanded.signatures,
|
||||
maxSignatures: expanded.maxSignatures
|
||||
};
|
||||
}
|
||||
const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output;
|
||||
return {
|
||||
prevOutType: SCRIPT_TYPES.P2PKH,
|
||||
prevOutScript: prevOutScript,
|
||||
hasWitness: false,
|
||||
signScript: prevOutScript,
|
||||
signType: SCRIPT_TYPES.P2PKH,
|
||||
pubkeys: [ourPubKey],
|
||||
signatures: [undefined]
|
||||
};
|
||||
}
|
||||
function build(type, input, allowIncomplete) {
|
||||
const pubkeys = (input.pubkeys || []);
|
||||
let signatures = (input.signatures || []);
|
||||
switch (type) {
|
||||
case SCRIPT_TYPES.P2PKH: {
|
||||
if (pubkeys.length === 0)
|
||||
break;
|
||||
if (signatures.length === 0)
|
||||
break;
|
||||
return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] });
|
||||
}
|
||||
case SCRIPT_TYPES.P2WPKH: {
|
||||
if (pubkeys.length === 0)
|
||||
break;
|
||||
if (signatures.length === 0)
|
||||
break;
|
||||
return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] });
|
||||
}
|
||||
case SCRIPT_TYPES.P2PK: {
|
||||
if (pubkeys.length === 0)
|
||||
break;
|
||||
if (signatures.length === 0)
|
||||
break;
|
||||
return payments.p2pk({ signature: signatures[0] });
|
||||
}
|
||||
case SCRIPT_TYPES.P2MS: {
|
||||
const m = input.maxSignatures;
|
||||
if (allowIncomplete) {
|
||||
signatures = signatures.map(x => x || script_1.OPS.OP_0);
|
||||
}
|
||||
else {
|
||||
signatures = signatures.filter(x => x);
|
||||
}
|
||||
// if the transaction is not not complete (complete), or if signatures.length === m, validate
|
||||
// otherwise, the number of OP_0's may be >= m, so don't validate (boo)
|
||||
const validate = !allowIncomplete || (m === signatures.length);
|
||||
return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate });
|
||||
}
|
||||
case SCRIPT_TYPES.P2SH: {
|
||||
const redeem = build(input.redeemScriptType, input, allowIncomplete);
|
||||
if (!redeem)
|
||||
return;
|
||||
return payments.p2sh({
|
||||
redeem: {
|
||||
output: redeem.output || input.redeemScript,
|
||||
input: redeem.input,
|
||||
witness: redeem.witness
|
||||
}
|
||||
});
|
||||
}
|
||||
case SCRIPT_TYPES.P2WSH: {
|
||||
const redeem = build(input.witnessScriptType, input, allowIncomplete);
|
||||
if (!redeem)
|
||||
return;
|
||||
return payments.p2wsh({
|
||||
redeem: {
|
||||
output: input.witnessScript,
|
||||
input: redeem.input,
|
||||
witness: redeem.witness
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function canSign(input) {
|
||||
return input.signScript !== undefined &&
|
||||
input.signType !== undefined &&
|
||||
input.pubkeys !== undefined &&
|
||||
input.signatures !== undefined &&
|
||||
input.signatures.length === input.pubkeys.length &&
|
||||
input.pubkeys.length > 0 &&
|
||||
(input.hasWitness === false ||
|
||||
input.value !== undefined);
|
||||
}
|
||||
function signatureHashType(buffer) {
|
||||
return buffer.readUInt8(buffer.length - 1);
|
||||
}
|
48
src/types.js
Normal file
48
src/types.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const typeforce = require('typeforce');
|
||||
const UINT31_MAX = Math.pow(2, 31) - 1;
|
||||
function UInt31(value) {
|
||||
return typeforce.UInt32(value) && value <= UINT31_MAX;
|
||||
}
|
||||
exports.UInt31 = UInt31;
|
||||
function BIP32Path(value) {
|
||||
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/);
|
||||
}
|
||||
exports.BIP32Path = BIP32Path;
|
||||
BIP32Path.toJSON = function () { return 'BIP32 derivation path'; };
|
||||
const SATOSHI_MAX = 21 * 1e14;
|
||||
function Satoshi(value) {
|
||||
return typeforce.UInt53(value) && value <= SATOSHI_MAX;
|
||||
}
|
||||
exports.Satoshi = Satoshi;
|
||||
// external dependent types
|
||||
exports.ECPoint = typeforce.quacksLike('Point');
|
||||
// exposed, external API
|
||||
exports.Network = typeforce.compile({
|
||||
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String),
|
||||
bip32: {
|
||||
public: typeforce.UInt32,
|
||||
private: typeforce.UInt32
|
||||
},
|
||||
pubKeyHash: typeforce.UInt8,
|
||||
scriptHash: typeforce.UInt8,
|
||||
wif: typeforce.UInt8
|
||||
});
|
||||
exports.Buffer256bit = typeforce.BufferN(32);
|
||||
exports.Hash160bit = typeforce.BufferN(20);
|
||||
exports.Hash256bit = typeforce.BufferN(32);
|
||||
exports.Number = typeforce.Number;
|
||||
exports.Array = typeforce.Array;
|
||||
exports.Boolean = typeforce.Boolean;
|
||||
exports.String = typeforce.String;
|
||||
exports.Buffer = typeforce.Buffer;
|
||||
exports.Hex = typeforce.Hex;
|
||||
exports.maybe = typeforce.maybe;
|
||||
exports.tuple = typeforce.tuple;
|
||||
exports.UInt8 = typeforce.UInt8;
|
||||
exports.UInt32 = typeforce.UInt32;
|
||||
exports.Function = typeforce.Function;
|
||||
exports.BufferN = typeforce.BufferN;
|
||||
exports.Null = typeforce.Null;
|
||||
exports.oneOf = typeforce.oneOf;
|
|
@ -1,7 +1,7 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const baddress = require('../dist/src/address')
|
||||
const bscript = require('../dist/src/script')
|
||||
const baddress = require('../src/address')
|
||||
const bscript = require('../src/script')
|
||||
const fixtures = require('./fixtures/address.json')
|
||||
const NETWORKS = Object.assign({
|
||||
litecoin: {
|
||||
|
@ -14,7 +14,7 @@ const NETWORKS = Object.assign({
|
|||
scriptHash: 0x32,
|
||||
wif: 0xb0
|
||||
}
|
||||
}, require('../dist/src/networks'))
|
||||
}, require('../src/networks'))
|
||||
|
||||
describe('address', function () {
|
||||
describe('fromBase58Check', function () {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bufferutils = require('../dist/src/bufferutils')
|
||||
const bufferutils = require('../src/bufferutils')
|
||||
|
||||
const fixtures = require('./fixtures/bufferutils.json')
|
||||
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bscript = require('../dist/src/script')
|
||||
const classify = require('../dist/src/classify')
|
||||
const bscript = require('../src/script')
|
||||
const classify = require('../src/classify')
|
||||
|
||||
const fixtures = require('./fixtures/templates.json')
|
||||
|
||||
const multisig = require('../dist/src/templates/multisig')
|
||||
const nullData = require('../dist/src/templates/nulldata')
|
||||
const pubKey = require('../dist/src/templates/pubkey')
|
||||
const pubKeyHash = require('../dist/src/templates/pubkeyhash')
|
||||
const scriptHash = require('../dist/src/templates/scripthash')
|
||||
const witnessPubKeyHash = require('../dist/src/templates/witnesspubkeyhash')
|
||||
const witnessScriptHash = require('../dist/src/templates/witnessscripthash')
|
||||
const witnessCommitment = require('../dist/src/templates/witnesscommitment')
|
||||
const multisig = require('../src/templates/multisig')
|
||||
const nullData = require('../src/templates/nulldata')
|
||||
const pubKey = require('../src/templates/pubkey')
|
||||
const pubKeyHash = require('../src/templates/pubkeyhash')
|
||||
const scriptHash = require('../src/templates/scripthash')
|
||||
const witnessPubKeyHash = require('../src/templates/witnesspubkeyhash')
|
||||
const witnessScriptHash = require('../src/templates/witnessscripthash')
|
||||
const witnessCommitment = require('../src/templates/witnesscommitment')
|
||||
|
||||
const tmap = {
|
||||
pubKey,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bcrypto = require('../dist/src/crypto')
|
||||
const bcrypto = require('../src/crypto')
|
||||
|
||||
const fixtures = require('./fixtures/crypto')
|
||||
|
||||
|
|
|
@ -5,12 +5,12 @@ const assert = require('assert')
|
|||
const proxyquire = require('proxyquire')
|
||||
const hoodwink = require('hoodwink')
|
||||
|
||||
const ECPair = require('../dist/src/ecpair')
|
||||
const ECPair = require('../src/ecpair')
|
||||
const tinysecp = require('tiny-secp256k1')
|
||||
|
||||
const fixtures = require('./fixtures/ecpair.json')
|
||||
|
||||
const NETWORKS = require('../dist/src/networks')
|
||||
const NETWORKS = require('../src/networks')
|
||||
const NETWORKS_LIST = [] // Object.values(NETWORKS)
|
||||
for (let networkName in NETWORKS) {
|
||||
NETWORKS_LIST.push(NETWORKS[networkName])
|
||||
|
@ -144,7 +144,7 @@ describe('ECPair', function () {
|
|||
describe('uses randombytes RNG', function () {
|
||||
it('generates a ECPair', function () {
|
||||
const stub = { randombytes: function () { return d } }
|
||||
const ProxiedECPair = proxyquire('../dist/src/ecpair', stub)
|
||||
const ProxiedECPair = proxyquire('../src/ecpair', stub)
|
||||
|
||||
const keyPair = ProxiedECPair.makeRandom()
|
||||
assert.strictEqual(keyPair.toWIF(), exWIF)
|
||||
|
|
|
@ -5,7 +5,7 @@ const u = require('./payments.utils')
|
|||
;['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(function (p) {
|
||||
describe(p, function () {
|
||||
let fn
|
||||
let payment = require('../dist/src/payments/' + p)
|
||||
let payment = require('../src/payments/' + p)
|
||||
if (p === 'embed') {
|
||||
fn = payment.p2data
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const t = require('assert')
|
||||
const bscript = require('../dist/src/script')
|
||||
const BNETWORKS = require('../dist/src/networks')
|
||||
const bscript = require('../src/script')
|
||||
const BNETWORKS = require('../src/networks')
|
||||
|
||||
function tryHex (x) {
|
||||
if (Buffer.isBuffer(x)) return x.toString('hex')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bscript = require('../dist/src/script')
|
||||
const bscript = require('../src/script')
|
||||
const minimalData = require('minimaldata')
|
||||
|
||||
const fixtures = require('./fixtures/script.json')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const scriptNumber = require('../dist/src/script_number')
|
||||
const scriptNumber = require('../src/script_number')
|
||||
const fixtures = require('./fixtures/script_number.json')
|
||||
|
||||
describe('script-number', function () {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bscriptSig = require('../dist/src/script').signature
|
||||
const bscriptSig = require('../src/script').signature
|
||||
const Buffer = require('safe-buffer').Buffer
|
||||
const fixtures = require('./fixtures/signature.json')
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { describe, it, beforeEach } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const bscript = require('../dist/src/script')
|
||||
const bscript = require('../src/script')
|
||||
const fixtures = require('./fixtures/transaction')
|
||||
const Transaction = require('..').Transaction
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
const { describe, it, beforeEach } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const baddress = require('../dist/src/address')
|
||||
const bscript = require('../dist/src/script')
|
||||
const payments = require('../dist/src/payments')
|
||||
const baddress = require('../src/address')
|
||||
const bscript = require('../src/script')
|
||||
const payments = require('../src/payments')
|
||||
|
||||
const ECPair = require('../dist/src/ecpair')
|
||||
const ECPair = require('../src/ecpair')
|
||||
const Transaction = require('..').Transaction
|
||||
const TransactionBuilder = require('..').TransactionBuilder
|
||||
const NETWORKS = require('../dist/src/networks')
|
||||
const NETWORKS = require('../src/networks')
|
||||
|
||||
const fixtures = require('./fixtures/transaction_builder')
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { describe, it } = require('mocha')
|
||||
const assert = require('assert')
|
||||
const types = require('../dist/src/types')
|
||||
const types = require('../src/types')
|
||||
const typeforce = require('typeforce')
|
||||
|
||||
describe('types', function () {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue