BLAKE2B works! Only one test vector so far, from the RFC

This commit is contained in:
DC 2016-01-10 00:36:26 -08:00
Родитель a975d18c5b
Коммит 32c95bc124
3 изменённых файлов: 148 добавлений и 89 удалений

Просмотреть файл

@ -5,19 +5,37 @@ Pure Javascript implementation of the BLAKE2B hash function.
```js
var Blake = require('blake')
console.log(Blake.blake2bHex('hello world'))
// prints ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923
```
// prints 00000000000000000000000000000000000000000000000000000000000000
console.log(Blake.blake2bStringToHex('hello world'))
API
---
Exports two functions:
// ascii values for 'hello world'
var input = new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100])
var output = Blake.blake2b(input)
var hex = Array.prototype.map.call(output, function(n) {
return (n < 16 ? '0' : '') + n.toString(16)
}).join('')
```js
// Computes the BLAKE2B hash of a string or byte array, and returns a Uint8Array
//
// Returns a n-byte Uint8Array
//
// Parameters:
// - input - the input bytes, as a Uint8Array or ASCII string
// - key - optional key, either a 32 or 64-byte Uint8Array
// - outlen - optional output length in bytes, default 64
function blake2b(input, key, outlen) {
[...]
}
// prints the same thing again
console.log(hex)
// Computes the BLAKE2B hash of a string or byte array
//
// Returns an n-byte hash in hex, all lowercase
//
// Parameters:
// - input - the input bytes, as a Uint8Array or ASCII string
// - outlen - optional output length in bytes, default 64
function blake2bHex(input, outlen) {
[...]
}
```
Limitations

Просмотреть файл

@ -1,29 +1,20 @@
// state context
/*
typedef struct {
uint8_t b[128]; // input buffer
uint64_t h[8]; // chained state
uint64_t t[2]; // total number of bytes
size_t c; // pointer for b[]
size_t outlen; // digest size
} blake2b_ctx;
*/
// Blake2B in pure Javascript
// Adapted from the reference implementation in RFC7693
// Ported to Javascript by DC - https://github.com/dcposch
// Cyclic 64-bit right rotation. x0 is low 32 bits, x1 is high 32 bits
// Returns [low 32 bit output, high 32 bit output]
function ROTR64(x, y) {
var x0 = x[0]
var x1 = x[1]
// uint64 rotation is NOT efficient if all you have is float64 :(
// (JIT compilied to signed int32, but still pretty terrible)
var o0 = (y < 32 ? x0 >> y : 0) ^
(y > 32 ? x1 >> (y - 32) : 0) ^
(y > 0 && y <= 32 ? x1 << (32 - y) : 0) ^
(y > 32 ? x0 << (64 - y) : 0)
var o1 = (y < 32 ? x1 >> y : 0) ^
(y > 32 ? x0 >> (y - 32) : 0) ^
// uint64 rotation is NOT efficient when all you have is float64 :(
// (it will JIT compile to signed int32, but it's still pretty terrible)
var o0 = (y < 32 ? x0 >>> y : 0) ^
(y > 32 ? x1 >>> (y - 32) : 0) ^
(y > 32 ? x0 << (64 - y) : 0) ^
(y > 0 && y <= 32 ? x1 << (32 - y) : 0)
var o1 = (y < 32 ? x1 >>> y : 0) ^
(y > 32 ? x0 >>> (y - 32) : 0) ^
(y > 32 ? x1 << (64 - y) : 0) ^
(y > 0 && y <= 32 ? x0 << (32 - y) : 0)
return [o0, o1]
@ -31,29 +22,32 @@ function ROTR64(x, y) {
// For debugging: prints out a uint64 represented as an array of two int32s
// Examples:
// HEX64([0x4030201, 0x8070605]) = "0x0807060504030201"
// HEX64(ROT64(0x4030201, 0x8070605, 16)) = "0x0201080706050403"
// HEX64([0x4030201, 0x8070605]) = '0x0807060504030201'
// HEX64(ROT64(0x4030201, 0x8070605, 16)) = '0x0201080706050403'
function HEX64(arr) {
var s0 = (arr[0] >= 0 ? arr[0] : 0x100000000 + arr[0]).toString(16)
var s1 = (arr[1] >= 0 ? arr[1] : 0x100000000 + arr[1]).toString(16)
return "0x" +
return '0x' +
new Array(8 - s1.length+1).join('0') + s1 +
new Array(8 - s0.length+1).join('0') + s0
}
// Little-endian byte access.
/*
#define B2B_GET64(p) \
(((uint64_t) ((uint8_t *) (p))[0]) ^ \
(((uint64_t) ((uint8_t *) (p))[1]) << 8) ^ \
(((uint64_t) ((uint8_t *) (p))[2]) << 16) ^ \
(((uint64_t) ((uint8_t *) (p))[3]) << 24) ^ \
(((uint64_t) ((uint8_t *) (p))[4]) << 32) ^ \
(((uint64_t) ((uint8_t *) (p))[5]) << 40) ^ \
(((uint64_t) ((uint8_t *) (p))[6]) << 48) ^ \
(((uint64_t) ((uint8_t *) (p))[7]) << 56))
*/
// For debugging: prints out hash state in the same format as the RFC
// sample computation
function DebugPrint(label, arr) {
var msg = '\n' + label + ' = '
for (var i = 0; i < arr.length; i++) {
msg += HEX64(arr[i]).substring(2).toUpperCase()
if (i % 3 === 2) {
msg += '\n' + new Array(label.length + 4).join(' ')
} else if (i < arr.length - 1) {
msg += ' '
}
}
console.log(msg)
}
// Little-endian byte access
function B2B_GET32(arr, i) {
return arr[i] ^
(arr[i+1]<<8) ^
@ -61,10 +55,8 @@ function B2B_GET32(arr, i) {
(arr[i+3]<<24)
}
/**
* Adds two little endian uint64s, arr[a,a+1], arr[b,b+1], stores in arr[x,x+1]
* Expects arr to be a Uint32Array
*/
//Adds two little endian uint64s, arr[a,a+1], arr[b,b+1], stores in arr[x,x+1]
//Expects arr to be a Uint32Array
function AADD64(arr, a, b, x) {
var o0 = arr[a] + arr[b]
var o1 = arr[a+1] + arr[b+1]
@ -75,10 +67,9 @@ function AADD64(arr, a, b, x) {
arr[x+1] = o1
}
/**
* Adds a small integer b to a little endian uint64 arr[a,a+1], stores in arr[x,x+1]
* Expects arr to be a Uint32Array
*/
//Adds a small integer b to a little endian uint64 arr[a,a+1], stores in
//arr[x,x+1]
//Expects arr to be a Uint32Array
function AADD64S(arr, a, b, x) {
var o0 = arr[a] + b
var o1 = arr[a+1]
@ -89,10 +80,23 @@ function AADD64S(arr, a, b, x) {
arr[x+1] = o1
}
// Javascript is just disgusting...
// var arr = new Uint32Array(10)
// arr[0] = 0xffffffff
// console.log(arr[0]) // prints 4 billion, all good
// console.log(arr[0] | 0) // prints -1
//
// So when working with 64-bit #s, the two 32-bit halves are signed int32s
function ADD64(a, b) {
var o0 = a[0] + b[0]
if(a[0] < 0) {
o0 += 0x100000000
}
if(b[0] < 0) {
o0 += 0x100000000
}
var o1 = a[1] + b[1]
if (a[0] + b[0] >= 0x100000000) {
if (o0 >= 0x100000000) {
o1++
}
return new Uint32Array([o0, o1])
@ -145,7 +149,7 @@ var SIGMA8 = new Uint8Array([
])
// Compression function. "last" flag indicates last block.
// Compression function. 'last' flag indicates last block.
function blake2b_compress(ctx, last) {
var i = 0
var v = new Array(16)
@ -158,13 +162,12 @@ function blake2b_compress(ctx, last) {
}
// low 64 bits of offset
v[12] = XOR64(v[12], ctx.t)
v[12] = XOR64(v[12], [ctx.t, 0])
// high 64 bits
//v[13] = XOR64(v[13], ctx.t[1])
// last block flag set ?
if (last) {
v[14][0] = ~v[14][0]
v[14][1] = ~v[14][1]
v[14] = [~v[14][0], ~v[14][1]]
}
// get little-endian words
@ -176,23 +179,29 @@ function blake2b_compress(ctx, last) {
}
// twelve rounds of mixing
// uncomment the DebugPrint calls to log the computation
// and match the RFC sample documentation
// DebugPrint(' m[16]', m)
for (i = 0; i < 12; i++) {
B2B_G(v, 0, 4, 8, 12, m[SIGMA8[i*16 + 0]], m[SIGMA8[i*16 + 1]]);
B2B_G(v, 1, 5, 9, 13, m[SIGMA8[i*16 + 2]], m[SIGMA8[i*16 + 3]]);
B2B_G(v, 2, 6, 10, 14, m[SIGMA8[i*16 + 4]], m[SIGMA8[i*16 + 5]]);
B2B_G(v, 3, 7, 11, 15, m[SIGMA8[i*16 + 6]], m[SIGMA8[i*16 + 7]]);
B2B_G(v, 0, 5, 10, 15, m[SIGMA8[i*16 + 8]], m[SIGMA8[i*16 + 9]]);
B2B_G(v, 1, 6, 11, 12, m[SIGMA8[i*16 +10]], m[SIGMA8[i*16 +11]]);
B2B_G(v, 2, 7, 8, 13, m[SIGMA8[i*16 +12]], m[SIGMA8[i*16 +13]]);
B2B_G(v, 3, 4, 9, 14, m[SIGMA8[i*16 +14]], m[SIGMA8[i*16 +15]]);
// DebugPrint(' (i='+(i<10?' ':'')+i+') v[16]', v)
B2B_G(v, 0, 4, 8, 12, m[SIGMA8[i*16 + 0]], m[SIGMA8[i*16 + 1]])
B2B_G(v, 1, 5, 9, 13, m[SIGMA8[i*16 + 2]], m[SIGMA8[i*16 + 3]])
B2B_G(v, 2, 6, 10, 14, m[SIGMA8[i*16 + 4]], m[SIGMA8[i*16 + 5]])
B2B_G(v, 3, 7, 11, 15, m[SIGMA8[i*16 + 6]], m[SIGMA8[i*16 + 7]])
B2B_G(v, 0, 5, 10, 15, m[SIGMA8[i*16 + 8]], m[SIGMA8[i*16 + 9]])
B2B_G(v, 1, 6, 11, 12, m[SIGMA8[i*16 +10]], m[SIGMA8[i*16 +11]])
B2B_G(v, 2, 7, 8, 13, m[SIGMA8[i*16 +12]], m[SIGMA8[i*16 +13]])
B2B_G(v, 3, 4, 9, 14, m[SIGMA8[i*16 +14]], m[SIGMA8[i*16 +15]])
}
// DebugPrint(' (i=12) v[16]', v)
for (i = 0; i < 8; i++) {
ctx.h[i] = XOR64(ctx.h[i], XOR64(v[i], v[i + 8]))
}
// DebugPrint('h[8]', ctx.h)
}
// Initialize the hashing context with optional key "key"
// Initialize the hashing context with optional key 'key'
// Returns a newly created context
function blake2b_init(outlen, key) {
if (outlen == 0 || outlen > 64) {
@ -202,7 +211,7 @@ function blake2b_init(outlen, key) {
throw new Error('Illegal key, expected Uint8Array with 0 < length <= 64')
}
// state, "param block"
// state, 'param block'
var ctx = {
b: new Uint8Array(128),
h: new Array(8),
@ -228,7 +237,7 @@ function blake2b_init(outlen, key) {
return ctx
}
// Add "in" into the hash. Expects a Uint8Array
// Add 'in' into the hash. Expects a Uint8Array
function blake2b_update(ctx, input) {
for (var i = 0; i < input.length; i++) {
if (ctx.c === 128) { // buffer full ?
@ -253,30 +262,48 @@ function blake2b_final(ctx) {
// little endian convert and store
var out = new Uint8Array(ctx.outlen)
for (var i = 0; i < ctx.outlen; i++) {
out[i] = (ctx.h[i >> 3] >> (8 * (i & 7))) & 0xFF;
out[i] = (ctx.h[i >> 3][(i >> 2) & 1] >> (8 * (i & 3))) & 0xFF;
}
return out
}
// Convenience function for all-in-one computation.
// Returns a outlen-byte Uint8Array
function blake2b(outlen, key, input) {
// Computes the BLAKE2B hash of a string or byte array, and returns a Uint8Array
//
// Returns a n-byte Uint8Array
//
// Parameters:
// - input - the input bytes, as a Uint8Array or ASCII string
// - key - optional key, either a 32 or 64-byte Uint8Array
// - outlen - optional output length in bytes, default 64
function blake2b(input, key, outlen) {
// preprocess inputs
outlen = outlen || 64
if (typeof(input) === 'string') {
var str = input
input = new Uint8Array(str.length)
for (var i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 255) {
throw new Error('Input must be an ASCII string or Uint8Array')
}
input[i] = str.charCodeAt(i)
}
}
// do the math
var ctx = blake2b_init(outlen, key)
blake2b_update(ctx, input)
return blake2b_final(ctx)
}
// More convenient convenience function
// Input string must be ASCII. Returns 32 byte hex digest.
function blake2bStringToHex(str, outlen) {
var input = new Uint8Array(str.length)
for (var i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 255) {
throw new Error('Input string must be ASCII')
}
input[i] = str.charCodeAt(i)
}
var output = blake2b(32, null, input)
// Computes the BLAKE2B hash of a string or byte array
//
// Returns an n-byte hash in hex, all lowercase
//
// Parameters:
// - input - the input bytes, as a Uint8Array or ASCII string
// - outlen - optional output length in bytes, default 64
function blake2bHex(input, outlen) {
var output = blake2b(input, null, outlen)
return Array.prototype.map.call(output, function(n) {
return (n < 16 ? '0' : '') + n.toString(16)
}).join('')
@ -284,5 +311,5 @@ function blake2bStringToHex(str, outlen) {
module.exports = {
blake2b: blake2b,
blake2bStringToHex: blake2bStringToHex
blake2bHex: blake2bHex
}

Просмотреть файл

@ -1,20 +1,34 @@
function testROTR64() {
assertEquals('0x0807060504030201', HEX64(ROTR64([0x4030201, 0x8070605], 0)))
assertEquals('0x0201080706050403', HEX64(ROTR64([0x4030201, 0x8070605], 16)))
assertEquals('0x4030201080706050', HEX64(ROTR64([0x4030201, 0x8070605], 28)))
assertEquals('0x1080706050403020', HEX64(ROTR64([0x4030201, 0x8070605], 4)))
assertEquals('0x0403020108070605', HEX64(ROTR64([0x4030201, 0x8070605], 32)))
assertEquals('0x8070605040302010', HEX64(ROTR64([0x4030201, 0x8070605], 60)))
assertEquals('0x7772fc2886a76c5f', HEX64(ROTR64([0x6c5f7772, 0xfc2886a7], 16)))
}
function testADD64() {
var ret = ADD64([0xffffffff, 1], [1, 0])
assertEquals(0, ret[0])
assertEquals(2, ret[1])
assertEquals('0x0000000200000000', HEX64(ADD64([0xffffffff, 1], [1, 0])))
assertEquals('0x00000002fffffffe', HEX64(ADD64([0xffffffff, 1], [0xffffffff, 0])))
assertEquals('0x76eb1310ddd333a0', HEX64(ADD64([0xF3BCC908, 0x6A09E667], [0xEA166A98, 0x0CE12CA8])))
assertEquals('0x76eb1310ddd333a0', HEX64(ADD64([-205731576, 1779033703], [-367629672, 216083624])))
}
function testXOR64() {
assertEquals('0x0000000600000002', HEX64(XOR64([1, 2], [3, 4])))
assertEquals('0x1234567812121212', HEX64(XOR64([0x10101010, 0x12005600], [0x02020202, 0x00340078])))
}
function testBLAKE2B() {
assertEquals('todo', blake2bStringToHex('hello'))
// From the example computation in the RFC
assertEquals('ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1' +
'7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923',
blake2bHex('abc'))
assertEquals('ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1' +
'7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923',
blake2bHex(new Uint8Array([97,98,99])))
}
function assertEquals(expected, actual) {