wip
This commit is contained in:
parent
6fd7173c89
commit
458e606359
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
npm-debug.log
|
||||
key.key
|
||||
node_modules/
|
||||
|
||||
@ -495,77 +495,6 @@ Pool.prototype._probeValidateCache = function probeValidateCache(tx) {
|
||||
}
|
||||
};
|
||||
|
||||
Pool.prototype.validateTx = function validateTx(tx, cb, _params) {
|
||||
// Probe cache first
|
||||
var result = this._probeValidateCache(tx);
|
||||
if (result) {
|
||||
bcoin.utils.nextTick(function() {
|
||||
cb(null, result);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not perform similar parallel validations
|
||||
if (!this.validate.reqs.add(tx.hash('hex'), cb))
|
||||
return;
|
||||
|
||||
if (!_params)
|
||||
_params = { depth: 0, range: null };
|
||||
|
||||
// Propagate range to improve speed of search
|
||||
var depth = _params.depth;
|
||||
var range = _params.range;
|
||||
|
||||
var result = {
|
||||
included: this.chain.hasMerkle(tx.hash()),
|
||||
valid: false
|
||||
};
|
||||
|
||||
console.log('validateTx: ', tx.hash('hex'), depth, result.included, _params.path);
|
||||
if (depth >= this.validate.minDepth && result.included) {
|
||||
result.valid = true;
|
||||
bcoin.utils.nextTick(function() {
|
||||
cb(null, result);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Load all inputs and validate them
|
||||
var self = this;
|
||||
async.map(tx.inputs, function(input, cb) {
|
||||
var out = null;
|
||||
self.getTx(input.out.hash, range, function(t, range) {
|
||||
out = t;
|
||||
self.validateTx(out, onSubvalidate, {
|
||||
depth: depth + 1,
|
||||
range: range,
|
||||
path: (_params.path || []).concat(tx.hash('hex'))
|
||||
});
|
||||
});
|
||||
|
||||
function onSubvalidate(err, subres) {
|
||||
if (err)
|
||||
return cb(err);
|
||||
|
||||
cb(null, {
|
||||
input: input,
|
||||
tx: out,
|
||||
valid: subres.valid
|
||||
});
|
||||
}
|
||||
}, function(err, inputs) {
|
||||
if (err) {
|
||||
result.valid = false;
|
||||
self.validate.reqs.fullfill(tx.hash('hex'), err, result);
|
||||
return cb(err, result);
|
||||
}
|
||||
|
||||
self._addValidateCache(tx, result);
|
||||
self.validate.reqs.fullfill(tx.hash('hex'), null, result);
|
||||
cb(null, result);
|
||||
});
|
||||
};
|
||||
|
||||
function LoadRequest(pool, type, hash, cb) {
|
||||
this.pool = pool
|
||||
this.type = type;
|
||||
|
||||
@ -43,3 +43,107 @@ exports.filterFlags = {
|
||||
all: 1,
|
||||
pubkeyOnly: 2
|
||||
};
|
||||
|
||||
exports.opcodes = {
|
||||
0: 0,
|
||||
pushdata1: 0x4c,
|
||||
pushdata2: 0x4d,
|
||||
pushdata4: 0x4e,
|
||||
negate1: 0x4f,
|
||||
|
||||
nop: 0x61,
|
||||
if_: 0x63,
|
||||
notif: 0x64,
|
||||
else_: 0x67,
|
||||
endif: 0x68,
|
||||
verify: 0x69,
|
||||
ret: 0x6a,
|
||||
|
||||
toaltstack: 0x6b,
|
||||
fromaltstack: 0x6c,
|
||||
ifdup: 0x73,
|
||||
depth: 0x74,
|
||||
drop: 0x75,
|
||||
dup: 0x76,
|
||||
nip: 0x77,
|
||||
over: 0x78,
|
||||
pick: 0x79,
|
||||
roll: 0x7a,
|
||||
rot: 0x7b,
|
||||
swap: 0x7c,
|
||||
tuck: 0x7d,
|
||||
drop2: 0x6d,
|
||||
dup2: 0x6e,
|
||||
dup3: 0x6f,
|
||||
over2: 0x70,
|
||||
rot2: 0x71,
|
||||
swap2: 0x72,
|
||||
|
||||
cat: 0x74,
|
||||
substr: 0x7f,
|
||||
left: 0x80,
|
||||
right: 0x81,
|
||||
size: 0x82,
|
||||
|
||||
invert: 0x83,
|
||||
and: 0x84,
|
||||
or: 0x85,
|
||||
xor: 0x86,
|
||||
eq: 0x87,
|
||||
eqverify: 0x88,
|
||||
|
||||
add1: 0x8b,
|
||||
sub1: 0x8c,
|
||||
mul2: 0x8d,
|
||||
div2: 0x8e,
|
||||
negate: 0x8f,
|
||||
abs: 0x90,
|
||||
not: 0x91,
|
||||
noteq0: 0x92,
|
||||
add: 0x93,
|
||||
sub: 0x94,
|
||||
mul: 0x95,
|
||||
div: 0x96,
|
||||
mod: 0x97,
|
||||
lshift: 0x98,
|
||||
rshift: 0x99,
|
||||
booland: 0x9a,
|
||||
boolor: 0x9b,
|
||||
numeq: 0x9c,
|
||||
numeqverify: 0x9d,
|
||||
numneq: 0x9e,
|
||||
lt: 0x9f,
|
||||
gt: 0xa0,
|
||||
lte: 0xa1,
|
||||
gte: 0xa2,
|
||||
min: 0xa3,
|
||||
max: 0xa4,
|
||||
within: 0xa5,
|
||||
|
||||
ripemd160: 0xa6,
|
||||
sha1: 0xa7,
|
||||
sha256: 0xa8,
|
||||
hash160: 0xa9,
|
||||
hash256: 0xaa,
|
||||
codesep: 0xab,
|
||||
checksig: 0xac,
|
||||
checksigverify: 0xad,
|
||||
checkmultisig: 0xae,
|
||||
checkmultisigverify: 0xaf
|
||||
};
|
||||
|
||||
for (var i = 1; i <= 16; i++)
|
||||
exports.opcodes[i] = 0x50 + i;
|
||||
|
||||
exports.opcodesByVal = new Array(256);
|
||||
Object.keys(exports.opcodes).forEach(function(name) {
|
||||
exports.opcodesByVal[exports.opcodes[name]] = name;
|
||||
});
|
||||
|
||||
// Little-endian hash type
|
||||
exports.hashType = {
|
||||
all: [ 1, 0, 0, 0 ],
|
||||
none: [ 2, 0, 0, 0 ],
|
||||
single: [ 3, 0, 0, 0 ],
|
||||
anyonecaypay: [ 0x80, 0, 0, 0 ],
|
||||
};
|
||||
|
||||
@ -211,3 +211,41 @@ Framer.prototype.getBlocks = function getBlocks(hashes, stop) {
|
||||
|
||||
return this.packet('getblocks', p);
|
||||
};
|
||||
|
||||
Framer.tx = function tx(tx) {
|
||||
var p = [];
|
||||
var off = writeU32(p, tx.version, 0);
|
||||
off += varint(p, tx.inputs.length, off);
|
||||
|
||||
for (var i = 0; i < tx.inputs.length; i++) {
|
||||
var input = tx.inputs[i];
|
||||
|
||||
off += utils.copy(utils.toArray(input.out.hash, 'hex'), p, off, true);
|
||||
off += writeU32(p, input.out.index, off);
|
||||
|
||||
var s = bcoin.script.encode(input.script);
|
||||
off += varint(p, s.length, off);
|
||||
off += utils.copy(s, p, off, true);
|
||||
|
||||
off += writeU32(p, input.seq, off);
|
||||
}
|
||||
|
||||
off += varint(p, tx.outputs.length, off);
|
||||
for (var i = 0; i < tx.outputs.length; i++) {
|
||||
var output = tx.outputs[i];
|
||||
|
||||
// Put LE value
|
||||
var value = output.value.toArray().slice().reverse();
|
||||
assert(value.length <= 8);
|
||||
off += utils.copy(value, p, off, true);
|
||||
for (var j = value.length; j < 8; j++, off++)
|
||||
p[off] = 0;
|
||||
|
||||
var s = bcoin.script.encode(output.script);
|
||||
off += varint(p, s.length, off);
|
||||
off += utils.copy(s, p, off, true);
|
||||
}
|
||||
off += writeU32(p, tx.lock, off);
|
||||
|
||||
return p;
|
||||
};
|
||||
|
||||
@ -1,5 +1,72 @@
|
||||
var bcoin = require('../bcoin');
|
||||
var constants = bcoin.protocol.constants;
|
||||
var utils = bcoin.protocol.utils;
|
||||
var script = exports;
|
||||
|
||||
script.parse = function parse(s) {
|
||||
return s;
|
||||
script.decode = function decode(s) {
|
||||
if (!s)
|
||||
return [];
|
||||
var opcodes = [];
|
||||
for (var i = 0; i < s.length;) {
|
||||
var b = s[i++];
|
||||
|
||||
// Next `b` bytes should be pushed to stack
|
||||
if (b >= 0x01 && b <= 0x75) {
|
||||
opcodes.push(s.slice(i, i + b));
|
||||
i += b;
|
||||
continue;
|
||||
}
|
||||
|
||||
var opcode = constants.opcodesByVal[b];
|
||||
if (opcode === 'pushdata1') {
|
||||
var len = s[i++];
|
||||
opcodes.push(s.slice(i, i + len));
|
||||
i += 2 + len;
|
||||
} else if (opcode === 'pushdata2') {
|
||||
var len = readU16(s, i);
|
||||
i += 2;
|
||||
opcodes.push(s.slice(i, i + len));
|
||||
i += len;
|
||||
} else if (opcode === 'pushdata4') {
|
||||
var len = readU32(s, i);
|
||||
i += 4;
|
||||
opcodes.push(s.slice(i, i + len));
|
||||
i += len;
|
||||
} else {
|
||||
opcodes.push(opcode || b);
|
||||
}
|
||||
}
|
||||
return opcodes;
|
||||
};
|
||||
|
||||
script.encode = function encode(s) {
|
||||
if (!s)
|
||||
return [];
|
||||
var opcodes = constants.opcodes;
|
||||
var res = [];
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
var instr = s[i];
|
||||
|
||||
// Push value to stack
|
||||
if (Array.isArray(instr)) {
|
||||
if (1 <= instr.length && instr.length <= 0x75) {
|
||||
res = res.concat(instr.length, instr);
|
||||
} else if (instr.length <= 0xff) {
|
||||
res = res.concat(opcodes['pushdata1'], instr.length, instr);
|
||||
} else if (instr.length <= 0xffff) {
|
||||
res.push(opcodes['pushdata2']);
|
||||
utils.writeU16(res, instr.length, res.length);
|
||||
res = res.concat(instr);
|
||||
} else {
|
||||
res.push(opcodes['pushdata4']);
|
||||
utils.writeU32(res, instr.length, res.length);
|
||||
res = res.concat(instr);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
res.push(opcodes[instr] || instr);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
var bn = require('bn.js');
|
||||
|
||||
var bcoin = require('../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
|
||||
@ -6,30 +8,34 @@ function TX(data) {
|
||||
return new TX(data);
|
||||
this.type = 'tx';
|
||||
|
||||
this.version = data.version;
|
||||
this.inputs = data.inputs.map(function(input) {
|
||||
return {
|
||||
out: {
|
||||
hash: utils.toHex(input.out.hash),
|
||||
index: bcoin.script.parse(input.out.index)
|
||||
},
|
||||
script: input.script,
|
||||
seq: input.seq
|
||||
};
|
||||
});
|
||||
this.outputs = data.outputs.map(function(output) {
|
||||
return {
|
||||
value: output.value,
|
||||
script: bcoin.script.parse(output.script)
|
||||
};
|
||||
});
|
||||
this.lock = data.lock;
|
||||
if (!data)
|
||||
data = {};
|
||||
|
||||
this.version = data.version || 1;
|
||||
this.inputs = [];
|
||||
this.outputs = [];
|
||||
this.lock = data.lock || 0;
|
||||
|
||||
this._hash = null;
|
||||
this._raw = data._raw || null;
|
||||
|
||||
if (data.inputs) {
|
||||
data.inputs.forEach(function(input) {
|
||||
this.input(input);
|
||||
}, this);
|
||||
}
|
||||
if (data.outputs) {
|
||||
data.outputs.forEach(function(out) {
|
||||
this.out(out);
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
module.exports = TX;
|
||||
|
||||
TX.prototype.clone = function clone() {
|
||||
return new TX(this);
|
||||
};
|
||||
|
||||
TX.prototype.hash = function hash(enc) {
|
||||
if (!this._hash) {
|
||||
// First, obtain the raw TX data
|
||||
@ -41,9 +47,60 @@ TX.prototype.hash = function hash(enc) {
|
||||
return enc === 'hex' ? utils.toHex(this._hash) : this._hash;
|
||||
};
|
||||
|
||||
TX.prototype.render = function render(framer) {
|
||||
return [];
|
||||
TX.prototype.render = function render() {
|
||||
return bcoin.protocol.framer.tx(this);
|
||||
};
|
||||
|
||||
TX.prototype.verify = function verify() {
|
||||
TX.prototype.input = function input(i, index) {
|
||||
if (i instanceof TX)
|
||||
i = { tx: i, index: i };
|
||||
|
||||
var hash;
|
||||
if (i.tx)
|
||||
hash = i.tx.hash('hex');
|
||||
else if (i.out)
|
||||
hash = i.out.hash;
|
||||
else
|
||||
hash = i.hash;
|
||||
|
||||
this.inputs.push({
|
||||
out: {
|
||||
tx: i.tx,
|
||||
hash: hash,
|
||||
index: i.out ? i.out.index : i.index,
|
||||
},
|
||||
script: bcoin.script.decode(i.script),
|
||||
seq: i.seq === undefined ? 0xffffffff : i.seq
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
TX.prototype.out = function out(output, value) {
|
||||
if (typeof output === 'string') {
|
||||
output = {
|
||||
address: output,
|
||||
value: value
|
||||
};
|
||||
}
|
||||
|
||||
var script = bcoin.script.decode(output.script);
|
||||
|
||||
// Default script if given address
|
||||
if (output.address) {
|
||||
script = [
|
||||
'dup',
|
||||
'hash160',
|
||||
bcoin.wallet.addr2hash(output.address),
|
||||
'eqverify',
|
||||
'checksig'
|
||||
];
|
||||
}
|
||||
|
||||
this.outputs.push({
|
||||
value: new bn(output.value),
|
||||
script: script
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@ -170,8 +170,10 @@ utils.writeAscii = function writeAscii(dst, str, off) {
|
||||
return i;
|
||||
};
|
||||
|
||||
utils.copy = function copy(src, dst, off) {
|
||||
var len = Math.min(dst.length - off, src.length);
|
||||
utils.copy = function copy(src, dst, off, force) {
|
||||
var len = src.length;
|
||||
if (!force)
|
||||
len = Math.min(dst.length - off, len);
|
||||
for (var i = 0; i < len; i++)
|
||||
dst[i + off] = src[i];
|
||||
return i;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
var assert = require('assert');
|
||||
var bcoin = require('../bcoin');
|
||||
var utils = bcoin.utils;
|
||||
|
||||
@ -9,18 +10,26 @@ function Wallet() {
|
||||
}
|
||||
module.exports = Wallet;
|
||||
|
||||
Wallet.prototype.getAddress = function getAddress() {
|
||||
Wallet.prototype.getHash = function getHash() {
|
||||
var pub = this.key.getPublic('array');
|
||||
var keyHash = utils.ripesha(pub);
|
||||
return utils.ripesha(pub);
|
||||
};
|
||||
|
||||
Wallet.prototype.getAddress = function getAddress() {
|
||||
return Wallet.hash2addr(this.getHash());
|
||||
};
|
||||
|
||||
Wallet.hash2addr = function hash2addr(hash) {
|
||||
hash = utils.toArray(hash, 'hex');
|
||||
|
||||
// Add version
|
||||
keyHash = [ 0 ].concat(keyHash);
|
||||
hash = [ 0 ].concat(hash);
|
||||
|
||||
var addr = keyHash.concat(utils.checksum(keyHash));
|
||||
var addr = hash.concat(utils.checksum(hash));
|
||||
return utils.toBase58(addr);
|
||||
}
|
||||
};
|
||||
|
||||
Wallet.prototype.validateAddress = function validateAddress(addr) {
|
||||
Wallet.addr2hash = function addr2hash(addr) {
|
||||
if (!Array.isArray(addr))
|
||||
addr = utils.fromBase58(addr);
|
||||
|
||||
@ -28,10 +37,56 @@ Wallet.prototype.validateAddress = function validateAddress(addr) {
|
||||
return false;
|
||||
if (addr[0] !== 0)
|
||||
return false;
|
||||
|
||||
var chk = utils.checksum(addr.slice(0, -4));
|
||||
if (utils.readU32(chk, 0) !== utils.readU32(addr, 21))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return addr.slice(1, -4);
|
||||
};
|
||||
|
||||
Wallet.prototype.validateAddress = function validateAddress(addr) {
|
||||
var p = Wallet.addr2hash(addr);
|
||||
return !!p;
|
||||
};
|
||||
Wallet.validateAddress = Wallet.prototype.validateAddress;
|
||||
|
||||
Wallet.prototype.own = function own(tx) {
|
||||
return tx.outputs.some(function(output) {
|
||||
return output.script.length === 5 &&
|
||||
output.script[0] === 'dup' &&
|
||||
output.script[1] === 'hash160' &&
|
||||
utils.toHex(output.script[2]) === utils.toHex(this.getHash()) &&
|
||||
output.script[3] === 'eqverify' &&
|
||||
output.script[4] === 'checksig';
|
||||
}, this);
|
||||
};
|
||||
|
||||
Wallet.prototype.sign = function sign(tx, type) {
|
||||
if (!type)
|
||||
type = 'all';
|
||||
assert.equal(type, 'all');
|
||||
|
||||
// Filter inputs that this wallet own
|
||||
var inputs = tx.inputs.filter(function(input) {
|
||||
return input.out.tx && this.own(input.out.tx);
|
||||
}, this);
|
||||
var pub = this.key.getPublic('array');
|
||||
|
||||
// Add signature script to each input
|
||||
inputs.forEach(function(input, i) {
|
||||
var copy = input.tx.clone();
|
||||
var s = input.out.tx.getSubscript();
|
||||
|
||||
copy.inputs.forEach(function(input, j) {
|
||||
input.script = i === j ? s : [];
|
||||
});
|
||||
|
||||
var verifyStr = copy.render();
|
||||
verifyStr = verifyStr.concat(bcoin.protocol.constants.hashType[type]);
|
||||
var hash = utils.dsha256(verifyStr);
|
||||
var signature = this.key.sign(hash).toDER();
|
||||
}, this);
|
||||
|
||||
return inputs.length;
|
||||
};
|
||||
|
||||
25
test/script-test.js
Normal file
25
test/script-test.js
Normal file
@ -0,0 +1,25 @@
|
||||
var assert = require('assert');
|
||||
var bcoin = require('../');
|
||||
|
||||
describe('Script', function() {
|
||||
it('should encode/decode script', function() {
|
||||
var src = '20' +
|
||||
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' +
|
||||
'20' +
|
||||
'101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f' +
|
||||
'ac';
|
||||
|
||||
var decoded = bcoin.script.decode(bcoin.utils.toArray(src, 'hex'));
|
||||
assert.equal(decoded.length, 3);
|
||||
assert.equal(
|
||||
bcoin.utils.toHex(decoded[0]),
|
||||
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f');
|
||||
assert.equal(
|
||||
bcoin.utils.toHex(decoded[1]),
|
||||
'101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f');
|
||||
assert.equal(decoded[2], 'checksig');
|
||||
|
||||
var dst = bcoin.script.encode(decoded);
|
||||
assert.equal(bcoin.utils.toHex(dst), src);
|
||||
});
|
||||
});
|
||||
20
test/tx-test.js
Normal file
20
test/tx-test.js
Normal file
@ -0,0 +1,20 @@
|
||||
var assert = require('assert');
|
||||
var bn = require('bn.js');
|
||||
var bcoin = require('../');
|
||||
|
||||
describe('TX', function() {
|
||||
var parser = bcoin.protocol.parser();
|
||||
|
||||
it('should decode/encode with parser/framer', function() {
|
||||
var raw = '010000000125393c67cd4f581456dd0805fa8e9db3abdf90dbe1d4b53e28' +
|
||||
'6490f35d22b6f2010000006b483045022100f4fa5ced20d2dbd2f905809d' +
|
||||
'79ebe34e03496ef2a48a04d0a9a1db436a211dd202203243d086398feb4a' +
|
||||
'c21b3b79884079036cd5f3707ba153b383eabefa656512dd0121022ebabe' +
|
||||
'fede28804b331608d8ef11e1d65b5a920720db8a644f046d156b3a73c0ff' +
|
||||
'ffffff0254150000000000001976a9140740345f114e1a1f37ac1cc442b4' +
|
||||
'32b91628237e88ace7d27b00000000001976a91495ad422bb5911c2c9fe6' +
|
||||
'ce4f82a13c85f03d9b2e88ac00000000';
|
||||
var tx = bcoin.tx(parser.parseTx(bcoin.utils.toArray(raw, 'hex')));
|
||||
assert.equal(bcoin.utils.toHex(tx.render()), raw);
|
||||
});
|
||||
});
|
||||
@ -16,4 +16,22 @@ describe('Wallet', function() {
|
||||
it('should fail to validate invalid address', function() {
|
||||
assert(!bcoin.wallet.validateAddress('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc'));
|
||||
});
|
||||
it('should sign/verify TX', function() {
|
||||
var w = bcoin.wallet();
|
||||
|
||||
// Input transcation
|
||||
var src = bcoin.tx({
|
||||
outputs: [{
|
||||
value: 5460 * 2,
|
||||
address: w.getAddress()
|
||||
}]
|
||||
});
|
||||
assert(w.own(src));
|
||||
|
||||
var tx = bcoin.tx()
|
||||
.input(src, 1)
|
||||
.out(w.getAddress(), 5460);
|
||||
|
||||
w.sign(tx);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user