Author

Topic: [5BTC bounty] Authenticate with MtGox API from Google Apps script (Read 2720 times)

sr. member
Activity: 304
Merit: 250
Excellent, it works. I've sent 5 bitcoins (d2b0d701566057bcf1dc0a87812f1f3d33f8962eb139d38cc46394bf65faa8cc).

Could you send a receipt for your work to [email protected] ?

Here's the code for future reference:
Code:
// https://github.com/Caligatio/jsSHA/blob/master/src/sha512.js
(function() {/*
 A JavaScript implementation of the SHA family of hashes, as defined in FIPS
 PUB 180-2 as well as the corresponding HMAC implementation as defined in
 FIPS PUB 198a

 Copyright Brian Turek 2008-2012
 Distributed under the BSD License
 See http://caligatio.github.com/jsSHA/ for more information

 Several functions taken from Paul Johnson
*/
function n(a){throw a;}var q=null;function s(a,b){this.a=a;this.b=b}function u(a,b){var d=[],h=(1<>>5]|=(a.charCodeAt(g/b)&h)<<32-b-g%32;return{value:d,binLen:f}}function x(a){var b=[],d=a.length,h,f;0!==d%2&&n("String of HEX type must be in byte increments");for(h=0;h>>3]|=f<<24-4*(h%8);return{value:b,binLen:4*d}}
function B(a){var b=[],d=0,h,f,g,k,m;-1===a.search(/^[a-zA-Z0-9=+\/]+$/)&&n("Invalid character in base-64 string");h=a.indexOf("=");a=a.replace(/\=/g,"");-1!==h&&h  // *     returns 1: 'foo=bar&php=hypertext+processor&baz=boom&cow=milk'
  // *     example 2: http_build_query({'php': 'hypertext processor', 0: 'foo', 1: 'bar', 2: 'baz', 3: 'boom', 'cow': 'milk'}, 'myvar_');
  // *     returns 2: 'php=hypertext+processor&myvar_0=foo&myvar_1=bar&myvar_2=baz&myvar_3=boom&cow=milk'
  var value, key, tmp = [],
    that = this;

  var _http_build_query_helper = function (key, val, arg_separator) {
    var k, tmp = [];
    if (val === true) {
      val = "1";
    } else if (val === false) {
      val = "0";
    }
    if (val != null) {
      if(typeof(val) === "object") {
        for (k in val) {
          if (val[k] != null) {
            tmp.push(_http_build_query_helper(key + "[" + k + "]", val[k], arg_separator));
          }
        }
        return tmp.join(arg_separator);
      } else if (typeof(val) !== "function") {
        return encodeURIComponent(key) + "=" + encodeURIComponent(val);
      } else {
        throw new Error('There was an error processing for http_build_query().');
      }
    } else {
      return '';
    }
  };

  if (!arg_separator) {
    arg_separator = "&";
  }
  for (key in formdata) {
    value = formdata[key];
    if (numeric_prefix && !isNaN(key)) {
      key = String(numeric_prefix) + key;
    }
    var query=_http_build_query_helper(key, value, arg_separator);
    if(query != '') {
      tmp.push(query);
    }
  }

  return tmp.join(arg_separator);
}

// End of code from phpjs

// ******* THE FUN STARTS HERE

function mtgoxApiCall(path, parameters) {
  var apiKey = "INSERT_KEY_HERE";
  var apiSecret = "INSERT_SECRET_HERE";

  parameters = (typeof parameters === "undefined") ? [] : parameters;

  // JavaScript only has millisecond time resolution, so we fake it 
  if (typeof mtgoxApiCall.nonce == 'undefined') {
    mtgoxApiCall.nonce = 0;
  }
  mtgoxApiCall.nonce = Math.max(Date.now() * 1000, mtgoxApiCall.nonce + 1);
  parameters["nonce"] = mtgoxApiCall.nonce.toFixed();

  var payload = http_build_query(parameters);

  var shaObj = new jsSHA(payload, "TEXT");
  var signature = shaObj.getHMAC(apiSecret, "B64", "SHA-512", "B64");
 
  var headers = {
    "User-Agent": "YOUR_USER_AGENT",
    "Rest-Key": apiKey,
    "Rest-Sign": signature
  }

  var options = {
    "method": "post",
    "headers": headers,
    "payload": payload
  };

  return UrlFetchApp.fetch("https://mtgox.com/api/" + path, options);
}

function mtgoxBuy () {
  parameters = {
    "type": "bid",
    "amount_int": 10000000,
    "price_int": 1000000
  };
  result = mtgoxApiCall("1/BTCUSD/order/add", parameters);
  result = Utilities.jsonParse(result.getContentText());
  Logger.log(result);
}
sr. member
Activity: 304
Merit: 250
Bounty increased to 5 BTC for a working solution.

I've done an alternate implementation, replacing Google's broken code with jsSHA. You may find it at the same URL as above. And finally it works. Enjoy and send the 5 BTC to the address below. Smiley
Awesome. It may be a day or two before I get around to test it.
hero member
Activity: 588
Merit: 500
Bounty increased to 5 BTC for a working solution.

I've done an alternate implementation, replacing Google's broken code with jsSHA. You may find it at the same URL as above. And finally it works. Enjoy and send the 5 BTC to the address below. Smiley
sr. member
Activity: 304
Merit: 250
Bounty increased to 5 BTC for a working solution.
hero member
Activity: 588
Merit: 500
You should test first, then. (Don't repeat my mistakes.) That workaround doesn't work.
legendary
Activity: 826
Merit: 1001
rippleFanatic
I've put a change up on Google (at the same URL) with the nonce fix. But there is still a problem with computing the HMAC signature. After a bunch of testing and a detour to Stack Overflow I've come to the conclusion that the reason it isn't working is due to a bug in Google Apps Script. As soon as a fix or workaround for this is available, the script should be good to go.

EDIT:
It might have to do with base64 encoded strings which lack padding. Some base64 decode methods work fine with unpadded strings, others don't. And some base64 encode methods don't add padding when they should. Just something to double-check (I had issues with this when using a python script for google 2-factor authentication).


Nevermind. Looks like the problem was determined in the Stack Overflow thread.

Here's the workaround (untested):

Code:
//signature = Utilities.base64Encode(Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_512, payload, Utilities.base64Decode(apiSecret)));
// replace the above line with below
signature = Utilities.base64Encode(Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_512, payload, convertBytesToString(Utilities.base64Decode(apiSecret)) ));


function convertBytesToString(bytesArray) {
  var stringFromBytes;
  for(var i=0; i < bytesArray.length; i++ ){ 
    stringFromBytes += String.fromCharCode(bytesArray[i]);
  }
  return stringFromBytes;
}
sr. member
Activity: 304
Merit: 250
Cool. Hope Google will get on the case.
hero member
Activity: 588
Merit: 500
I've put a change up on Google (at the same URL) with the nonce fix. But there is still a problem with computing the HMAC signature. After a bunch of testing and a detour to Stack Overflow I've come to the conclusion that the reason it isn't working is due to a bug in Google Apps Script. As soon as a fix or workaround for this is available, the script should be good to go.
hero member
Activity: 588
Merit: 500
From some quick testing it looks like Google's API function is returning the wrong result. I'm trying to figure out why, though I haven't been very successful so far (and their documentation isn't very good). As soon as I do, I'll post an update.

I also found a problem with the nonce so I'll be updating that as well.
sr. member
Activity: 304
Merit: 250
hero member
Activity: 588
Merit: 500
This solution relies on a very small part of the phpjs code, which is dual-licensed under GPL and MIT. Pick whichever you want. As a result, you may also consider this dual-licensed under GPL and MIT.

It uses the Google Apps Script UrlFetchApp class, and should handle both v0 and v1 API calls. Customize it by adding in your API key, secret and preferred user-agent string in the marked places.

It takes two parameters, path and parameters. path is the API call (e.g. "0/sellBTC.php" or "1/private/BTCUSD/order/add"). parameters is an associative array containing the key/value pairs of the parameters to be passed to the API call.

Regrettably, this is only minimally tested since I have no need to create any orders right now, and MtGox doesn't seem to provide a sandbox. Nevertheless it should be about 99% correct, with only one or two bugs, which I presume you will report so I can fix them. Smiley Once that's done, you can send the BTC to the address in my signature.

Example (to sell 1 BTC at $13.50):

Code:
parameters = {
  "type": "ask",
  "amount_int": 100000000,
  "price_int": 1350000
};
result = jsonParse(mtgoxApiCall("1/BTCUSD/order/add", parameters));

View or download this script: https://script.google.com/d/10ZDAQq87gbAItZhrYV_B0ldH_l5kLU3BlwsG6xch_0PWMOiadO_gYUz0/edit
sr. member
Activity: 304
Merit: 250
Bounty increased to 3BTC.
sr. member
Activity: 304
Merit: 250
Code:
public static void submit_order(String amount_int,String price_int){
  
  HashMap args=new HashMap();
  args.put("type","bid");
  DecimalFormat myFormatter=new DecimalFormat(".00000000");
  args.put("amount_int", myFormatter.format(Double.parseDouble(amount_int)).replace(".", ""));
  myFormatter = new DecimalFormat(".00000");
  args.put("price_int", myFormatter.format(Double.parseDouble(price_int)).replace(".", ""));
  query("1/BTCUSD/private/order/add",args,"PUT_API_KEY_HERE","PUT_API_SECRET_HERE");
}

private static String query(String path, HashMap args, String apikey, String apisecret) {
        try {
            args.put("nonce", String.valueOf(System.currentTimeMillis()));
            String post_data = buildQueryString(args);
            Mac mac = Mac.getInstance("HmacSHA512");
            SecretKeySpec secret_spec = new SecretKeySpec(Base64.decodeBase64(apisecret), "HmacSHA512");
            mac.init(secret_spec);
            String signature = Base64.encodeBase64String(mac.doFinal(post_data.getBytes()));
            URL queryUrl = new URL("https://mtgox.com/api/" + path);
            HttpURLConnection connection = (HttpURLConnection)queryUrl.openConnection();
            connection.setDoOutput(true);
            connection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; Java Test client)");
            connection.setRequestProperty("Rest-Key", apikey);
            connection.setRequestProperty("Rest-Sign", signature.replaceAll("\n", ""));
            connection.getOutputStream().write(post_data.getBytes());
            byte buffer[] = new byte[16384];
            int len = connection.getInputStream().read(buffer, 0, 16384);
            return new String(buffer, 0, len, "UTF-8");
        } catch (Exception ex) {
        
        }
return null;
    }
 
    private static String buildQueryString(HashMap args) {
        String result = new String();
        for (String hashkey : args.keySet()) {
            if (result.length() > 0) result += '&';
            try {
                result += URLEncoder.encode(hashkey, "UTF-8") + "="
                        + URLEncoder.encode(args.get(hashkey), "UTF-8");
            } catch (Exception ex) {
            
            }
        }
        return result;
    }

Hi

It doesn't run. This looks like Java while the Google Apps scripting language is much like Javascript.

EDIT: The above was a reply to a post that has been removed.
sr. member
Activity: 304
Merit: 250
My hacking skills are pretty basic and I normally hire people to code for my projects. But now I'm playing around with a Google Apps script and MtGox for fun and I can't figure out how to authenticate with API key.

Because MtGox API v1 requires a user agent which looks difficult to supply from Google Apps, I figure it better be done using MtGox API v0.

I'll pay 5BTC if you supply me with code that when run from Google Apps will authenticate with MtGox and place one buy order.

Update: This has been solved and the bounty paid.
Jump to: