Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
Merge branch 'segwit' of git://github.com/braydonf/btcjs into braydon…
Browse files Browse the repository at this point in the history
…f-segwit
  • Loading branch information
nitsujlangston committed Mar 11, 2018
2 parents 062baa9 + 559520a commit 5dbd275
Show file tree
Hide file tree
Showing 15 changed files with 1,247 additions and 40 deletions.
10 changes: 7 additions & 3 deletions lib/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,16 @@ Address._transformScript = function(script, network) {
* @param {Array} publicKeys - a set of public keys to create an address
* @param {number} threshold - the number of signatures needed to release the funds
* @param {String|Network} network - either a Network instance, 'livenet', or 'testnet'
* @param {boolean=} nestedWitness - if the address uses a nested p2sh witness
* @return {Address}
*/
Address.createMultisig = function(publicKeys, threshold, network) {
Address.createMultisig = function(publicKeys, threshold, network, nestedWitness) {
network = network || publicKeys[0].network || Networks.defaultNetwork;
return Address.payingTo(Script.buildMultisigOut(publicKeys, threshold), network);
var redeemScript = Script.buildMultisigOut(publicKeys, threshold);
if (nestedWitness) {
return Address.payingTo(Script.buildWitnessMultisigOutFromScript(redeemScript), network);
}
return Address.payingTo(redeemScript, network);
};

/**
Expand Down Expand Up @@ -336,7 +341,6 @@ Address.fromScriptHash = function(hash, network) {
Address.payingTo = function(script, network) {
$.checkArgument(script, 'script is required');
$.checkArgument(script instanceof Script, 'script must be instance of Script');

return Address.fromScriptHash(Hash.sha256ripemd160(script.toBuffer()), network);
};

Expand Down
144 changes: 139 additions & 5 deletions lib/script/interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,80 @@ var Interpreter = function Interpreter(obj) {
}
};

Interpreter.prototype.verifyWitnessProgram = function(version, program, witness, satoshis, flags) {
var scriptPubKey = new Script();
var stack = [];

if (version === 0) {
if (program.length === 32) {
if (witness.length === 0) {
this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY';
return false;
}

var scriptPubKeyBuffer = witness[witness.length - 1];
scriptPubKey = new Script(scriptPubKeyBuffer);
var hash = Hash.sha256(scriptPubKeyBuffer);
if (hash.toString('hex') !== program.toString('hex')) {
this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH';
return false;
}

stack = witness.slice(0, -1);
} else if (program.length === 20) {
if (witness.length !== 2) {
this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH';
return false;
}

scriptPubKey.add(Opcode.OP_DUP);
scriptPubKey.add(Opcode.OP_HASH160);
scriptPubKey.add(program);
scriptPubKey.add(Opcode.OP_EQUALVERIFY);
scriptPubKey.add(Opcode.OP_CHECKSIG);

stack = witness;

} else {
this.errstr = 'SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH';
return false;
}
} else if ((flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)) {
this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM';
return false;
} else {
return true;
}

this.initialize();

this.set({
script: scriptPubKey,
stack: stack,
sigversion: 1,
satoshis: satoshis
});

if (!this.evaluate()) {
return false;
}

if (this.stack.length !== 1) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE';
return false;
}

var buf = this.stack[this.stack.length - 1];
if (!Interpreter.castToBool(buf)) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK';
return false;
}

return true;
};



/**
* Verifies a Script by executing it and returns true if it is valid.
* This function needs to be provided with the scriptSig and the scriptPubkey
Expand All @@ -41,10 +115,13 @@ var Interpreter = function Interpreter(obj) {
* to check signature validity for some opcodes like OP_CHECKSIG)
* @param {number} nin - index of the transaction input containing the scriptSig verified.
* @param {number} flags - evaluation flags. See Interpreter.SCRIPT_* constants
* @param {number} witness - array of witness data
* @param {number} satoshis - number of satoshis created by this output
*
* Translated from bitcoind's VerifyScript
*/
Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags) {
Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags, witness, satoshis) {

var Transaction = require('../transaction');
if (_.isUndefined(tx)) {
tx = new Transaction();
Expand All @@ -55,10 +132,19 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags)
if (_.isUndefined(flags)) {
flags = 0;
}
if (_.isUndefined(witness)) {
witness = null;
}
if (_.isUndefined(satoshis)) {
satoshis = 0;
}

this.set({
script: scriptSig,
tx: tx,
nin: nin,
sigversion: 0,
satoshis: 0,
flags: flags
});
var stackCopy;
Expand Down Expand Up @@ -103,6 +189,22 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags)
return false;
}

var hadWitness = false;

if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) {
var witnessValues = {};
if (scriptPubkey.isWitnessProgram(witnessValues)) {
hadWitness = true;
if (scriptSig.toBuffer().length !== 0) {
return false;
}

if (!this.verifyWitnessProgram(witnessValues.version, witnessValues.program, witness, satoshis, flags)) {
return false;
}
}
}

// Additional validation for spend-to-script-hash transactions:
if ((flags & Interpreter.SCRIPT_VERIFY_P2SH) && scriptPubkey.isScriptHashOut()) {
// scriptSig must be literals-only or validation fails
Expand Down Expand Up @@ -144,8 +246,31 @@ Interpreter.prototype.verify = function(scriptSig, scriptPubkey, tx, nin, flags)
if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) {
this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK';
return false;
} else {
return true;
}
if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) {
var p2shWitnessValues = {};
if (redeemScript.isWitnessProgram(p2shWitnessValues)) {
hadWitness = true;
var redeemScriptPush = new Script();
redeemScriptPush.add(redeemScript.toBuffer());
if (scriptSig.toHex() !== redeemScriptPush.toHex()) {
this.errstr = 'SCRIPT_ERR_WITNESS_MALLEATED_P2SH';
return false;
}

if (!this.verifyWitnessProgram(p2shWitnessValues.version, p2shWitnessValues.program, witness, satoshis, flags)) {
return false;
}

stack = [stack[0]];
}
}

if ((flags & Interpreter.SCRIPT_VERIFY_WITNESS)) {
if (!hadWitness && witness.length > 0) {
this.errstr = 'SCRIPT_ERR_WITNESS_UNEXPECTED';
return false;
}
}
}

Expand All @@ -158,6 +283,8 @@ Interpreter.prototype.initialize = function(obj) {
this.stack = [];
this.altstack = [];
this.pc = 0;
this.satoshis = 0;
this.sigversion = 0;
this.pbegincodehash = 0;
this.nOpCount = 0;
this.vfExec = [];
Expand All @@ -173,6 +300,8 @@ Interpreter.prototype.set = function(obj) {
this.altstack = obj.altack || this.altstack;
this.pc = typeof obj.pc !== 'undefined' ? obj.pc : this.pc;
this.pbegincodehash = typeof obj.pbegincodehash !== 'undefined' ? obj.pbegincodehash : this.pbegincodehash;
this.sigversion = typeof obj.sigversion !== 'undefined' ? obj.sigversion : this.sigversion;
this.satoshis = typeof obj.satoshis !== 'undefined' ? obj.satoshis : this.satoshis;
this.nOpCount = typeof obj.nOpCount !== 'undefined' ? obj.nOpCount : this.nOpCount;
this.vfExec = obj.vfExec || this.vfExec;
this.errstr = obj.errstr || this.errstr;
Expand All @@ -191,6 +320,9 @@ Interpreter.LOCKTIME_THRESHOLD_BN = new BN(Interpreter.LOCKTIME_THRESHOLD);
// bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104
Interpreter.SCRIPT_VERIFY_NONE = 0;

// Making v1-v16 witness program non-standard
Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = (1 << 12);

// Evaluate P2SH subscripts (softfork safe, BIP16).
Interpreter.SCRIPT_VERIFY_P2SH = (1 << 0);

Expand Down Expand Up @@ -231,6 +363,8 @@ Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7);

// CLTV See BIP65 for details.
Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = (1 << 9);
Interpreter.SCRIPT_VERIFY_WITNESS = (1 << 10);
Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 11);

Interpreter.castToBool = function(buf) {
for (var i = 0; i < buf.length; i++) {
Expand Down Expand Up @@ -1111,7 +1245,7 @@ Interpreter.prototype.step = function() {
try {
sig = Signature.fromTxFormat(bufSig);
pubkey = PublicKey.fromBuffer(bufPubkey, false);
fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript);
fSuccess = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion, this.satoshis);
} catch (e) {
//invalid sig or pubkey
fSuccess = false;
Expand Down Expand Up @@ -1200,7 +1334,7 @@ Interpreter.prototype.step = function() {
try {
sig = Signature.fromTxFormat(bufSig);
pubkey = PublicKey.fromBuffer(bufPubkey, false);
fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript);
fOk = this.tx.verifySignature(sig, pubkey, this.nin, subscript, this.sigversion, this.satoshis);
} catch (e) {
//invalid sig or pubkey
fOk = false;
Expand Down
63 changes: 63 additions & 0 deletions lib/script/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,49 @@ Script.prototype.isScriptHashOut = function() {
buf[buf.length - 1] === Opcode.OP_EQUAL);
};

/**
* @returns {boolean} if this is a p2wsh output script
*/
Script.prototype.isWitnessScriptHashOut = function() {
var buf = this.toBuffer();
return (buf.length === 34 && buf[0] === 0 && buf[1] === 32);
};

/**
* @returns {boolean} if this is a p2wpkh output script
*/
Script.prototype.isWitnessPublicKeyHashOut = function() {
var buf = this.toBuffer();
return (buf.length === 22 && buf[0] === 0 && buf[1] === 20);
};

/**
* @param {Object=} values - The return values
* @param {Number} values.version - Set with the witness version
* @param {Buffer} values.program - Set with the witness program
* @returns {boolean} if this is a p2wpkh output script
*/
Script.prototype.isWitnessProgram = function(values) {
if (!values) {
values = {};
}
var buf = this.toBuffer();
if (buf.length < 4 || buf.length > 42) {
return false;
}
if (buf[0] !== Opcode.OP_0 && !(buf[0] >= Opcode.OP_1 && buf[0] <= Opcode.OP_16)) {
return false;
}

if (buf.length === buf[1] + 2) {
values.version = buf[0];
values.program = buf.slice(2, buf.length);
return true;
}

return false;
};

/**
* @returns {boolean} if this is a p2sh input script
* Note that these are frequently indistinguishable from pubkeyhashin
Expand Down Expand Up @@ -689,6 +732,15 @@ Script.prototype._addBuffer = function(buf, prepend) {
return this;
};

Script.prototype.hasCodeseparators = function() {
for (var i = 0; i < this.chunks.length; i++) {
if (this.chunks[i].opcodenum === Opcode.OP_CODESEPARATOR) {
return true;
}
}
return false;
};

Script.prototype.removeCodeseparators = function() {
var chunks = [];
for (var i = 0; i < this.chunks.length; i++) {
Expand Down Expand Up @@ -733,6 +785,17 @@ Script.buildMultisigOut = function(publicKeys, threshold, opts) {
return script;
};

Script.buildWitnessMultisigOutFromScript = function(script) {
if (script instanceof Script) {
var s = new Script();
s.add(Opcode.OP_0);
s.add(Hash.sha256(script.toBuffer()));
return s;
} else {
throw new TypeError('First argument is expected to be a p2sh script');
}
};

/**
* A new Multisig input script for the given public keys, requiring m of those public keys to spend
*
Expand Down
1 change: 1 addition & 0 deletions lib/transaction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ module.exports.Output = require('./output');
module.exports.UnspentOutput = require('./unspentoutput');
module.exports.Signature = require('./signature');
module.exports.Sighash = require('./sighash');
module.exports.SighashWitness = require('./sighashwitness');
23 changes: 23 additions & 0 deletions lib/transaction/input/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Input.prototype._fromObject = function(params) {
} else {
prevTxId = params.prevTxId;
}
this.witnesses = [];
this.output = params.output ?
(params.output instanceof Output ? params.output : new Output(params.output)) : undefined;
this.prevTxId = prevTxId || params.txidbuf;
Expand Down Expand Up @@ -153,6 +154,13 @@ Input.prototype.getSignatures = function() {
);
};

Input.prototype.getSatoshisBuffer = function() {
$.checkState(this.output instanceof Output);
$.checkState(this.output._satoshisBN);
return new BufferWriter().writeUInt64LEBN(this.output._satoshisBN).toBuffer();
};


Input.prototype.isFullySigned = function() {
throw new errors.AbstractMethodInvoked('Input#isFullySigned');
};
Expand All @@ -169,6 +177,21 @@ Input.prototype.clearSignatures = function() {
throw new errors.AbstractMethodInvoked('Input#clearSignatures');
};

Input.prototype.hasWitnesses = function() {
if (this.witnesses && this.witnesses.length > 0) {
return true;
}
return false;
};

Input.prototype.getWitnesses = function() {
return this.witnesses;
};

Input.prototype.setWitnesses = function(witnesses) {
this.witnesses = witnesses;
};

Input.prototype.isValidSignature = function(transaction, signature) {
// FIXME: Refactor signature so this is not necessary
signature.signature.nhashtype = signature.sigtype;
Expand Down
Loading

0 comments on commit 5dbd275

Please sign in to comment.