Skip to content

Commit

Permalink
added safeStringify and safeJsonParse functions for hashing before si…
Browse files Browse the repository at this point in the history
…gning and verifying
  • Loading branch information
thantsintoe committed Nov 20, 2024
1 parent a98140b commit 756d38b
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 3 deletions.
8 changes: 5 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const nacl = require('tweetnacl');
const blake = require('blakejs');
const stringify = require('json-stable-stringify')
const {safeJsonParse, safeStringify} = require('./utils/stringify')
nacl.util = require('tweetnacl-util'); // Utility functions for encoding/decoding

let HASH_KEY;
Expand Down Expand Up @@ -46,7 +46,7 @@ function hashObj (obj, removeSign = false) {
throw TypeError('Input must be an object.')
}
function performHash (obj) {
let input = stringify(obj)
let input = safeStringify(obj)
let hashed = hash(input)
return hashed
}
Expand Down Expand Up @@ -150,7 +150,7 @@ function signObj(obj, secretKey, publicKey) {
}

// Step 1: Hash the object using the hashObj function
let objStr = stringify(obj);
let objStr = safeStringify(obj);
let hashed = hash(objStr); // Hash the object and return a hex string

// Step 2: Sign the hashed object
Expand Down Expand Up @@ -213,3 +213,5 @@ exports.sign = sign;
exports.signObj = signObj;
exports.verify = verify;
exports.verifyObj = verifyObj;
exports.safeStringify = safeStringify;
exports.safeJsonParse = safeJsonParse;
24 changes: 24 additions & 0 deletions tests/stringify.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const stringify = require('../utils/stringify')
const { safeJsonParse, safeStringify } = stringify

// Generate test data in Node.js
function generateNodeData() {
const data = {
// Generate a random byteArray using crypto
data: new Uint8Array([1, 2, 3]),
// Create a large BigInt (similar to what you might use for cryptography)
nonce: BigInt("0x123456789abcdef123456789abcdef"),
// Add some normal data too
timestamp: Date.now(),
message: "Hello from Node.js!"
};
return data;
}

const data = generateNodeData()

const stringified = safeStringify(data)
console.log('Stringified data:', stringified)

const parsed = safeJsonParse(stringified)
console.log('Parsed data:', parsed)
192 changes: 192 additions & 0 deletions utils/stringify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory();
} else {
// Browser globals
root.stringifyUtils = factory();
}
}(typeof self !== 'undefined' ? self : this, function() {

const objToString = Object.prototype.toString

const objKeys = Object.keys || function(obj) {
const keys = []
for (const name in obj) {
keys.push(name)
}
return keys
}

const isObject = (val) => {
if (val === null) {
return false
}
if (Array.isArray(val)) {
return false
}
return typeof val === 'function' || typeof val === 'object'
}

const isUint8Array = (val) => {
return val instanceof Uint8Array
}

const uint8ArrayToBase64 = (uint8Array) => {
let binary = ''
for (let i = 0; i < uint8Array.length; i++) {
binary += String.fromCharCode(uint8Array[i])
}
return btoa(binary)
}

const base64ToUint8Array = (base64) => {
const binary = atob(base64)
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}

function safeStringify(val, options = { bufferEncoding: 'base64' }) {
const returnVal = stringifyHelper(val, false, options)
if (returnVal !== undefined) {
return '' + returnVal
}
}

function safeJsonParse(value) {
return JSON.parse(value, typeReviver)
}

function isBufferValue(toStr, val) {
return (
toStr === '[object Object]' &&
objKeys(val).length === 2 &&
objKeys(val).includes('type') &&
val['type'] === 'Buffer'
)
}

function stringifyHelper(val, isArrayProp, options = { bufferEncoding: 'base64' }) {
if (options === null) options = { bufferEncoding: 'base64' }
let i, max, str, keys, key, propVal, toStr

if (val === true) {
return 'true'
}
if (val === false) {
return 'false'
}

switch (typeof val) {
case 'object':
if (val === null) {
return null
} else if ('toJSON' in val && typeof val.toJSON === 'function') {
return stringifyHelper(val.toJSON(), isArrayProp, options)
} else {
toStr = objToString.call(val)
if (toStr === '[object Array]') {
str = '['
max = val.length - 1
for (i = 0; i < max; i++) {
str += stringifyHelper(val[i], true) + ','
}
if (max > -1) {
str += stringifyHelper(val[i], true)
}
return str + ']'
} else if (isUint8Array(val)) {
return JSON.stringify({
value: uint8ArrayToBase64(val),
dataType: 'u8ab'
})
} else if (
options.bufferEncoding !== 'none' &&
isBufferValue(toStr, val)
) {
switch (options.bufferEncoding) {
case 'base64':
return JSON.stringify({
value: uint8ArrayToBase64(new Uint8Array(val['data'])),
dataType: 'bb'
})
}
} else if (toStr === '[object Object]') {
keys = objKeys(val).sort()
max = keys.length
str = ''
i = 0
while (i < max) {
key = keys[i]
propVal = stringifyHelper(val[key], false, options)
if (propVal !== undefined) {
if (str) {
str += ','
}
str += JSON.stringify(key) + ':' + propVal
}
i++
}
return '{' + str + '}'
} else {
return JSON.stringify(val)
}
}
case 'function':
case 'undefined':
return isArrayProp ? null : undefined
case 'string':
return JSON.stringify(val)
case 'bigint':
return JSON.stringify({ dataType: 'bi', value: val.toString(16) })
default:
return isFinite(val) ? val : null
}
}

function getUint8ArrayFromField(input, encoding) {
switch (encoding) {
case 'base64':
return base64ToUint8Array(input.value)
default:
return new Uint8Array(input)
}
}

function typeReviver(key, value) {
if (key === 'sig') return value
const originalObject = value
if (
isObject(originalObject) &&
Object.prototype.hasOwnProperty.call(originalObject, 'dataType') &&
originalObject.dataType
) {
if (originalObject.dataType === 'bb' || originalObject.dataType === 'u8ab') {
if (typeof originalObject.value !== 'string') {
return value
}
return originalObject.dataType === 'bb'
? getUint8ArrayFromField(originalObject, 'base64')
: base64ToUint8Array(originalObject.value)
} else if (originalObject.dataType === 'bi') {
return BigInt('0x' + originalObject.value)
} else {
return value
}
} else {
return value
}
}

// Return public API
return {
safeStringify,
safeJsonParse
}
}));

0 comments on commit 756d38b

Please sign in to comment.