-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add primitives base58 and sha256 (#16)
- Loading branch information
1 parent
eba4324
commit 31b3de9
Showing
7 changed files
with
391 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// Original js implementation https://gist.github.com/diafygi/90a3e80ca1c2793220e5/ | ||
|
||
const base58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; | ||
|
||
export const base58FromBytes = ( | ||
input: Uint8Array //Uint8Array raw byte input | ||
): string => { | ||
const d = []; //the array for storing the stream of base58 digits | ||
let s = ''; //the result string variable that will be returned | ||
let j = 0; //the iterator variable for the base58 digit array (d) | ||
let c = 0; //the carry amount variable that is used to overflow from the current base58 digit to the next base58 digit | ||
let n: number; //a temporary placeholder variable for the current base58 digit | ||
for (let i = 0; i < input.length; i++) { | ||
//loop through each byte in the input stream | ||
j = 0; //reset the base58 digit iterator | ||
c = input[i]; //set the initial carry amount equal to the current byte amount | ||
s += c || s.length ^ i ? '' : '1'; //prepend the result string with a "1" (0 in base58) if the byte stream is zero and non-zero bytes haven't been seen yet (to ensure correct decode length) | ||
while (j in d || c) { | ||
//start looping through the digits until there are no more digits and no carry amount | ||
n = d[j]; //set the placeholder for the current base58 digit | ||
n = n ? n * 256 + c : c; //shift the current base58 one byte and add the carry amount (or just add the carry amount if this is a new digit) | ||
c = (n / 58) | 0; //find the new carry amount (floored integer of current digit divided by 58) | ||
d[j] = n % 58; //reset the current base58 digit to the remainder (the carry amount will pass on the overflow) | ||
j++; //iterate to the next base58 digit | ||
} | ||
} | ||
while (j--) | ||
//since the base58 digits are backwards, loop through them in reverse order | ||
s += base58[d[j]]; //lookup the character associated with each base58 digit | ||
return s; //return the final base58 string | ||
}; | ||
|
||
export const base58ToBytes = ( | ||
str: string //Base58 encoded string input | ||
): Uint8Array => { | ||
const d = []; //the array for storing the stream of decoded bytes | ||
const b = []; //the result byte array that will be returned | ||
let j = 0; //the iterator variable for the byte array (d) | ||
let c = 0; //the carry amount variable that is used to overflow from the current byte to the next byte | ||
let n = 0; //a temporary placeholder variable for the current byte | ||
for (let i = 0; i < str.length; i++) { | ||
//loop through each base58 character in the input string | ||
j = 0; //reset the byte iterator | ||
c = base58.indexOf(str[i]); //set the initial carry amount equal to the current base58 digit | ||
if (c < 0) | ||
//see if the base58 digit lookup is invalid (-1) | ||
throw new Error(`Can't convert base58 string ${str} to bytes`); | ||
c || b.length ^ i ? i : b.push(0); //prepend the result array with a zero if the base58 digit is zero and non-zero characters haven't been seen yet (to ensure correct decode length) | ||
while (j in d || c) { | ||
//start looping through the bytes until there are no more bytes and no carry amount | ||
n = d[j]; //set the placeholder for the current byte | ||
n = n ? n * 58 + c : c; //shift the current byte 58 units and add the carry amount (or just add the carry amount if this is a new byte) | ||
c = n >> 8; //find the new carry amount (1-byte shift of current byte value) | ||
d[j] = n % 256; //reset the current byte to the remainder (the carry amount will pass on the overflow) | ||
j++; //iterate to the next byte | ||
} | ||
} | ||
while (j--) | ||
//since the byte array is backwards, loop through it in reverse order | ||
b.push(d[j]); //append each byte to the result | ||
return new Uint8Array(b); //return the final byte array in Uint8Array format | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
// SHA-256 for JavaScript. | ||
// Original implementation https://github.com/dchest/fast-sha256-js/blob/master/src/sha256.ts | ||
const digestLength = 32; | ||
const blockSize = 64; | ||
|
||
// SHA-256 constants | ||
const K = new Uint32Array([ | ||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, | ||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, | ||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | ||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, | ||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, | ||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | ||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, | ||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 | ||
]); | ||
|
||
function hashBlocks(w: Int32Array, v: Int32Array, p: Uint8Array, pos: number, len: number): number { | ||
let a: number, | ||
b: number, | ||
c: number, | ||
d: number, | ||
e: number, | ||
f: number, | ||
g: number, | ||
h: number, | ||
u: number, | ||
i: number, | ||
j: number, | ||
t1: number, | ||
t2: number; | ||
while (len >= 64) { | ||
a = v[0]; | ||
b = v[1]; | ||
c = v[2]; | ||
d = v[3]; | ||
e = v[4]; | ||
f = v[5]; | ||
g = v[6]; | ||
h = v[7]; | ||
|
||
for (i = 0; i < 16; i++) { | ||
j = pos + i * 4; | ||
w[i] = | ||
((p[j] & 0xff) << 24) | | ||
((p[j + 1] & 0xff) << 16) | | ||
((p[j + 2] & 0xff) << 8) | | ||
(p[j + 3] & 0xff); | ||
} | ||
|
||
for (i = 16; i < 64; i++) { | ||
u = w[i - 2]; | ||
t1 = ((u >>> 17) | (u << (32 - 17))) ^ ((u >>> 19) | (u << (32 - 19))) ^ (u >>> 10); | ||
|
||
u = w[i - 15]; | ||
t2 = ((u >>> 7) | (u << (32 - 7))) ^ ((u >>> 18) | (u << (32 - 18))) ^ (u >>> 3); | ||
|
||
w[i] = ((t1 + w[i - 7]) | 0) + ((t2 + w[i - 16]) | 0); | ||
} | ||
|
||
for (i = 0; i < 64; i++) { | ||
t1 = | ||
((((((e >>> 6) | (e << (32 - 6))) ^ | ||
((e >>> 11) | (e << (32 - 11))) ^ | ||
((e >>> 25) | (e << (32 - 25)))) + | ||
((e & f) ^ (~e & g))) | | ||
0) + | ||
((h + ((K[i] + w[i]) | 0)) | 0)) | | ||
0; | ||
|
||
t2 = | ||
((((a >>> 2) | (a << (32 - 2))) ^ | ||
((a >>> 13) | (a << (32 - 13))) ^ | ||
((a >>> 22) | (a << (32 - 22)))) + | ||
((a & b) ^ (a & c) ^ (b & c))) | | ||
0; | ||
|
||
h = g; | ||
g = f; | ||
f = e; | ||
e = (d + t1) | 0; | ||
d = c; | ||
c = b; | ||
b = a; | ||
a = (t1 + t2) | 0; | ||
} | ||
|
||
v[0] += a; | ||
v[1] += b; | ||
v[2] += c; | ||
v[3] += d; | ||
v[4] += e; | ||
v[5] += f; | ||
v[6] += g; | ||
v[7] += h; | ||
|
||
pos += 64; | ||
len -= 64; | ||
} | ||
return pos; | ||
} | ||
|
||
// Hash implements SHA256 hash algorithm. | ||
export class Hash { | ||
digestLength: number = digestLength; | ||
blockSize: number = blockSize; | ||
|
||
// Note: Int32Array is used instead of Uint32Array for performance reasons. | ||
private state: Int32Array = new Int32Array(8); // hash state | ||
private temp: Int32Array = new Int32Array(64); // temporary state | ||
private buffer: Uint8Array = new Uint8Array(128); // buffer for data to hash | ||
private bufferLength = 0; // number of bytes in buffer | ||
private bytesHashed = 0; // number of total bytes hashed | ||
|
||
finished = false; // indicates whether the hash was finalized | ||
|
||
constructor() { | ||
this.reset(); | ||
} | ||
|
||
// Resets hash state making it possible | ||
// to re-use this instance to hash other data. | ||
reset(): this { | ||
this.state[0] = 0x6a09e667; | ||
this.state[1] = 0xbb67ae85; | ||
this.state[2] = 0x3c6ef372; | ||
this.state[3] = 0xa54ff53a; | ||
this.state[4] = 0x510e527f; | ||
this.state[5] = 0x9b05688c; | ||
this.state[6] = 0x1f83d9ab; | ||
this.state[7] = 0x5be0cd19; | ||
this.bufferLength = 0; | ||
this.bytesHashed = 0; | ||
this.finished = false; | ||
return this; | ||
} | ||
|
||
// Cleans internal buffers and re-initializes hash state. | ||
clean() { | ||
for (let i = 0; i < this.buffer.length; i++) { | ||
this.buffer[i] = 0; | ||
} | ||
for (let i = 0; i < this.temp.length; i++) { | ||
this.temp[i] = 0; | ||
} | ||
this.reset(); | ||
} | ||
|
||
// Updates hash state with the given data. | ||
// | ||
// Optionally, length of the data can be specified to hash | ||
// fewer bytes than data.length. | ||
// | ||
// Throws error when trying to update already finalized hash: | ||
// instance must be reset to use it again. | ||
update(data: Uint8Array, dataLength: number = data.length): this { | ||
if (this.finished) { | ||
throw new Error("SHA256: can't update because hash was finished."); | ||
} | ||
let dataPos = 0; | ||
this.bytesHashed += dataLength; | ||
if (this.bufferLength > 0) { | ||
while (this.bufferLength < 64 && dataLength > 0) { | ||
this.buffer[this.bufferLength++] = data[dataPos++]; | ||
dataLength--; | ||
} | ||
if (this.bufferLength === 64) { | ||
hashBlocks(this.temp, this.state, this.buffer, 0, 64); | ||
this.bufferLength = 0; | ||
} | ||
} | ||
if (dataLength >= 64) { | ||
dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength); | ||
dataLength %= 64; | ||
} | ||
while (dataLength > 0) { | ||
this.buffer[this.bufferLength++] = data[dataPos++]; | ||
dataLength--; | ||
} | ||
return this; | ||
} | ||
|
||
// Finalizes hash state and puts hash into out. | ||
// | ||
// If hash was already finalized, puts the same value. | ||
finish(out: Uint8Array): this { | ||
if (!this.finished) { | ||
const bytesHashed = this.bytesHashed; | ||
const left = this.bufferLength; | ||
const bitLenHi = (bytesHashed / 0x20000000) | 0; | ||
const bitLenLo = bytesHashed << 3; | ||
const padLength = bytesHashed % 64 < 56 ? 64 : 128; | ||
|
||
this.buffer[left] = 0x80; | ||
for (let i = left + 1; i < padLength - 8; i++) { | ||
this.buffer[i] = 0; | ||
} | ||
this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff; | ||
this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff; | ||
this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff; | ||
this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff; | ||
this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff; | ||
this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff; | ||
this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff; | ||
this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff; | ||
|
||
hashBlocks(this.temp, this.state, this.buffer, 0, padLength); | ||
|
||
this.finished = true; | ||
} | ||
|
||
for (let i = 0; i < 8; i++) { | ||
out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff; | ||
out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff; | ||
out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff; | ||
out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff; | ||
} | ||
|
||
return this; | ||
} | ||
|
||
// Returns the final hash digest. | ||
digest(): Uint8Array { | ||
const out = new Uint8Array(this.digestLength); | ||
this.finish(out); | ||
return out; | ||
} | ||
|
||
// Internal function for use in HMAC for optimization. | ||
_saveState(out: Uint32Array) { | ||
for (let i = 0; i < this.state.length; i++) { | ||
out[i] = this.state[i]; | ||
} | ||
} | ||
|
||
// Internal function for use in HMAC for optimization. | ||
_restoreState(from: Uint32Array, bytesHashed: number) { | ||
for (let i = 0; i < this.state.length; i++) { | ||
this.state[i] = from[i]; | ||
} | ||
this.bytesHashed = bytesHashed; | ||
this.finished = false; | ||
this.bufferLength = 0; | ||
} | ||
} | ||
|
||
export function sha256(data: Uint8Array): Uint8Array { | ||
const h = new Hash().update(data); | ||
const digest = h.digest(); | ||
h.clean(); | ||
return digest; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { Hex } from '../src'; | ||
import { base58ToBytes, base58FromBytes } from '../src/base58'; | ||
describe('base58', () => { | ||
it('base58 to binary', () => { | ||
const inp = '6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV'; | ||
const expected = '02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfeb05f9d2'; | ||
const inHex = Hex.encodeString(base58ToBytes(inp)); | ||
expect(inHex).toEqual(expected); | ||
expect(() => base58ToBytes('0L')).toThrowError(`Can't convert base58 string 0L to bytes`); | ||
}); | ||
|
||
it('base58 to binary', () => { | ||
expect( | ||
base58FromBytes( | ||
Hex.decodeString( | ||
'02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfeb05f9d2' | ||
) | ||
) | ||
).toEqual('6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV'); | ||
expect(base58FromBytes(Uint8Array.from([0, 0, 0, 4]))).toBe('1115'); | ||
}); | ||
}); |
Oops, something went wrong.