Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add primitives base58 and sha256 #16

Merged
merged 1 commit into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@iden3/js-crypto",
"version": "1.0.1",
"version": "1.0.2",
"description": "Crypto primitives for iden3",
"source": "./src/index.ts",
"exports": {
Expand Down
62 changes: 62 additions & 0 deletions src/base58.ts
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
};
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ export * from './babyjub';
export * from './poseidon';
export * from './hex';
export * from './blake';
export * from './base58';
export * from './sha256';
export { utils as ffUtils } from './ff';
252 changes: 252 additions & 0 deletions src/sha256.ts
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;
}
22 changes: 22 additions & 0 deletions tests/base58.test.ts
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');
});
});
Loading