Convert Node.JS code snippet to Javascript (Google Apps Script)
Asked Answered
D

3

5

I would like to convert the following Node.JS code snippet to JavaScript in order to run it in Google Apps Script:

From: Node.JS

function getMessageSignature(path, request, nonce) {
  var message   = querystring.stringify(request);
  var secret    = new Buffer(config.secret, 'base64');
  var hash  = new crypto.createHash('sha256');
  var hmac  = new crypto.createHmac('sha512', secret);
  var hash_digest   = hash.update(nonce + message).digest('binary');
  var hmac_digest   = hmac.update(path + hash_digest, 'binary').digest('base64');
  return hmac_digest;
}

This is the code I have tried so far (and many variations of it):

To: JavaScript / Google Apps Script

function getMessageSignature(url, request, nonce) {

  // Message signature using HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) 
  //and base64 decoded secret API key

  const secretApiKey = 'wdwdKswdKKewe23edeYIvL/GsltsGWbuBXnarcxZfu/9PjFbXl5npg==';
  var secretApiKeyBytes = Utilities.base64Decode(secretApiKey);
  var blob = Utilities.newBlob(secretApiKeyBytes); 
  var secretApiKeyString = blob.getDataAsString(); // POTENTIAL ERROR HERE?

  var json = Utilities.jsonStringify(request); 

  var hash_digest = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, 
      nonce + json);

  var hmac_digest = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_512, 
      url + hash_digest, secretApiKeyString); // POTENTIAL ERROR HERE?

  var base64 = Utilities.base64Encode(hmac_digest);

  return base64;
}

When sending the signature as part of my request to the server, I always get the error message from the server: Invalid Key.

BTW: This is the API which I would like to use in JavaScript: Kraken API

I would appreciate any hint or suggestions very much!!

Dostie answered 12/4, 2014 at 16:29 Comment(1)
I've been banging my head against this one all night. Please post back with your solution if you've figured it out. The cryptographic functions in NodeJS and Google App Script seem to differ in output.Likely
K
2

One problem is that querystring.stringify is not the same as Utilities.jsonStringify (which, FYI, is deprecated in favor of JSON.stringify).

I believe that this will be equivalent:

function queryStringify(obj) {
    var params = [];
    for(var key in obj) {
        if(Object.hasOwnProperty(key)) {
           if(typeof key === 'string') {
               params.push([key, obj[key]]);
           } else {
               obj[key].forEach(function(val) {
                   params.push([key, val]);
               });
           }
        }
    }
    return params.map(function(param) {
        return encodeURIComponent(param[0]) + '=' + encodeURIComponent(param[1]);
    }).join('&');
}

Though I am not sure if that is the reason you are seeing your error.

Kilometer answered 12/4, 2014 at 16:38 Comment(2)
Thank you for your answer, Paul! I believe the error is somewhere else because I get the error even when I "hard code" the JSONified string in my request. I believe my problem could come from url + hash_digest which is a string operation but should by a byte operation.Dostie
@Dostie Any luck getting this to work? I'm trying to do the same thing.Likely
E
4

Solution:

Use jsSHA (https://github.com/Caligatio/jsSHA/) rather than Google App Script's functions. Create a new "jsSHA.gs" code file in Google App Script and copy/past in all the jsSHA optimised .js files from github.

function getKrakenSignature (path, postdata, nonce) {
  var sha256obj = new jsSHA ("SHA-256", "BYTES");
  sha256obj.update (nonce + postdata);
  var hash_digest = sha256obj.getHash ("BYTES");

  var sha512obj = new jsSHA ("SHA-512", "BYTES");
  sha512obj.setHMACKey (api_key_private, "B64");
  sha512obj.update (path);
  sha512obj.update (hash_digest);
  return sha512obj.getHMAC ("B64");
}

function getKrakenBalance () {
  var path = "/0/private/Balance";
  var nonce = new Date () * 1000;
  var postdata = "nonce=" + nonce;

  var signature = getKrakenSignature (path, postdata, nonce);

  var url = api_url + path;
  var options = {
    method: 'post',
    headers: {
      'API-Key': api_key_public,
      'API-Sign': signature
    },
    payload: postdata
  };

  var response = UrlFetchApp.fetch (url, options);

  // ERROR handling

  return response.getContentText ();
}
Erotica answered 26/5, 2017 at 12:26 Comment(1)
You are a life saver :wink:Jalousie
K
2

One problem is that querystring.stringify is not the same as Utilities.jsonStringify (which, FYI, is deprecated in favor of JSON.stringify).

I believe that this will be equivalent:

function queryStringify(obj) {
    var params = [];
    for(var key in obj) {
        if(Object.hasOwnProperty(key)) {
           if(typeof key === 'string') {
               params.push([key, obj[key]]);
           } else {
               obj[key].forEach(function(val) {
                   params.push([key, val]);
               });
           }
        }
    }
    return params.map(function(param) {
        return encodeURIComponent(param[0]) + '=' + encodeURIComponent(param[1]);
    }).join('&');
}

Though I am not sure if that is the reason you are seeing your error.

Kilometer answered 12/4, 2014 at 16:38 Comment(2)
Thank you for your answer, Paul! I believe the error is somewhere else because I get the error even when I "hard code" the JSONified string in my request. I believe my problem could come from url + hash_digest which is a string operation but should by a byte operation.Dostie
@Dostie Any luck getting this to work? I'm trying to do the same thing.Likely
S
2

I noticed this nodejs to GS converter: https://www.npmjs.com/package/codegs

Haven't got the chance to use it, but it claims to handle 'require'-statements.

Snowberry answered 2/6, 2017 at 5:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.