diff --git a/lib/bcoin/siphash.js b/lib/bcoin/siphash.js new file mode 100644 index 00000000..27cc84bf --- /dev/null +++ b/lib/bcoin/siphash.js @@ -0,0 +1,184 @@ +/*! + * siphash.js - siphash for bcoin + * Copyright (c) 2016, Christopher Jeffrey (MIT License). + * https://github.com/indutny/bcoin + * + * Ported from: + * https://github.com/bitcoin/bitcoin/blob/master/src/hash.cpp + */ + +/** + * Javascript siphash implementation. Used for compact block relay. + * @param {Buffer} data - Blocks are uint64le's. + * @param {Buffer} k0 - Must be encoded as a uint64le. + * @param {Buffer} k1 - Must be encoded as a uint64le. + * @returns {Buffer} uint64le + */ + +function siphash(data, k0, k1) { + var out = new Buffer(8); + var blocks = Math.ceil(data.length / 8); + var c0 = { hi: 0x736f6d65, lo: 0x70736575 }; + var c1 = { hi: 0x646f7261, lo: 0x6e646f6d }; + var c2 = { hi: 0x6c796765, lo: 0x6e657261 }; + var c3 = { hi: 0x74656462, lo: 0x79746573 }; + var f0 = { hi: blocks << 27, lo: 0 }; + var f1 = { hi: 0, lo: 0xff }; + var i, d, v0, v1, v2, v3; + + k0 = read(k0, 0); + k1 = read(k1, 0); + + // Init + v0 = xor64(c0, k0); + v1 = xor64(c1, k1); + v2 = xor64(c2, k0); + v3 = xor64(c3, k1); + + // Blocks + for (i = 0; i < blocks; i++) { + d = read(data, i * 8); + xor64(v3, d); + sipround(v0, v1, v2, v3); + sipround(v0, v1, v2, v3); + xor64(v0, d); + } + + // Finalization + xor64(v3, f0); + sipround(v0, v1, v2, v3); + sipround(v0, v1, v2, v3); + xor64(v0, f0); + xor64(v2, f1); + sipround(v0, v1, v2, v3); + sipround(v0, v1, v2, v3); + sipround(v0, v1, v2, v3); + sipround(v0, v1, v2, v3); + xor64(v0, v1); + xor64(v0, v2); + xor64(v0, v3); + + write(out, v0, 0); + + return out; +} + +function sipround(v0, v1, v2, v3) { + sum64(v0, v1); + rotl64(v1, 13); + xor64(v1, v0); + + rotl64(v0, 32); + + sum64(v2, v3); + rotl64(v3, 16); + xor64(v3, v2); + + sum64(v0, v3); + rotl64(v3, 21); + xor64(v3, v0); + + sum64(v2, v1); + rotl64(v1, 17); + xor64(v1, v2); + + rotl64(v2, 32); +} + +/* + * Helpers + */ + +function sum64(a, b) { + var r, carry; + + r = a.lo + b.lo; + carry = (r - (r % 0x100000000)) / 0x100000000; + a.hi = (a.hi + b.hi + carry) & 0xffffffff; + a.lo = r & 0xffffffff; + + if (a.hi < 0) + a.hi += 0x100000000; + + if (a.lo < 0) + a.lo += 0x100000000; + + return a; +} + +function rotl64(x, b) { + var hi, lo, h1, l1, h2, l2, c; + + // v1 = x << b + if (b < 32) { + h1 = x.hi << b; + c = x.lo >>> (32 - b); + l1 = x.lo << b; + h1 |= c; + } else { + h1 = x.lo << (b - 32); + l1 = 0; + } + + // v2 = x >> (64 - b) + b = 64 - b; + if (b < 32) { + h2 = x.hi >>> b; + c = x.hi & (0xffffffff >>> (32 - b)); + l2 = x.lo >>> b; + l2 |= c << (32 - b); + } else { + h2 = 0; + l2 = x.hi >>> (b - 32); + } + + // v1 | v2 + hi = h1 | h2; + lo = l1 | l2; + + x.hi = hi; + x.lo = lo; + + if (x.hi < 0) + x.hi += 0x100000000; + + if (x.lo < 0) + x.lo += 0x100000000; + + return x; +} + +function xor64(a, b) { + a.hi ^= b.hi; + a.lo ^= b.lo; + + if (a.hi < 0) + a.hi += 0x100000000; + + if (a.lo < 0) + a.lo += 0x100000000; + + return a; +} + +function read(data, off) { + return { + hi: data.readUInt32LE(off + 4, true), + lo: data.readUInt32LE(off, true) + }; +} + +function write(data, value, off) { + data.writeUInt32LE(value.hi, off + 4, true); + data.writeUInt32LE(value.lo, off, true); +} + +/* + * Expose + */ + +exports = siphash; +exports.write = write; +exports.read = read; + +module.exports = exports; diff --git a/test/siphash-test.js b/test/siphash-test.js new file mode 100644 index 00000000..623792c3 --- /dev/null +++ b/test/siphash-test.js @@ -0,0 +1,40 @@ +var assert = require('assert'); +var siphash = require('../lib/bcoin/siphash'); + +describe('SipHash', function() { + it('should perform siphash with no data', function() { + var k0 = new Buffer(8); + var k1 = new Buffer(8); + siphash.write(k0, { hi: 0x07060504, lo: 0x03020100 }, 0); + siphash.write(k1, { hi: 0x0F0E0D0C, lo: 0x0B0A0908 }, 0); + // be: + // assert.equal(siphash(k0, k1, new Buffer(0)).toString('hex'), '726fdb47dd0e0e31'); + // le: + assert.equal(siphash(new Buffer(0), k0, k1).toString('hex'), '310e0edd47db6f72'); + }); + + it('should perform siphash with data', function() { + var k0 = new Buffer(8); + var k1 = new Buffer(8); + var data = new Buffer(8); + siphash.write(k0, { hi: 0x07060504, lo: 0x03020100 }, 0); + siphash.write(k1, { hi: 0x0F0E0D0C, lo: 0x0B0A0908 }, 0); + siphash.write(data, { hi: 0x07060504, lo: 0x03020100 }, 0); + // be: + // assert.equal(siphash(k0, k1, data).toString('hex'), '93f5f5799a932462'); + // le: + assert.equal(siphash(data, k0, k1).toString('hex'), '6224939a79f5f593'); + }); + + it('should perform siphash with uint256', function() { + var k0 = new Buffer(8); + var k1 = new Buffer(8); + siphash.write(k0, { hi: 0x07060504, lo: 0x03020100 }, 0); + siphash.write(k1, { hi: 0x0F0E0D0C, lo: 0x0B0A0908 }, 0); + var hash = new Buffer('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', 'hex'); + // be: + // assert.equal(siphash(k0, k1, hash).toString('hex'), '7127512f72f27cce'); + // le: + assert.equal(siphash(hash, k0, k1).toString('hex'), 'ce7cf2722f512771'); + }); +});