UNPKG

safex-addressjs

Version:

Generate and checksum safex addresses

438 lines (402 loc) 16.4 kB
const JSBigInt = require('./JSBigInt'); const {mn_random} = require('./mnemonic'); const Module = require('./ecmascripten'); const cnBase58 = require('./cnBase58'); const SAFEX_ADDRESS_PREFIX = 0x10003798; function CnUtil() { var HASH_STATE_BYTES = 200; var HASH_SIZE = 32; var ADDRESS_CHECKSUM_SIZE = 4; var CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = SAFEX_ADDRESS_PREFIX; var UINT64_MAX = new JSBigInt(2).pow(64); var CURRENT_TX_VERSION = 1; var TX_EXTRA_NONCE_MAX_COUNT = 255; var TX_EXTRA_TAGS = { PADDING: '00', PUBKEY: '01', NONCE: '02', MERGE_MINING: '03' }; var TX_EXTRA_NONCE_TAGS = { PAYMENT_ID: '00' }; var KEY_SIZE = 32; var STRUCT_SIZES = { GE_P3: 160, GE_P2: 120, GE_P1P1: 160, GE_CACHED: 160, EC_SCALAR: 32, EC_POINT: 32, KEY_IMAGE: 32, GE_DSMP: 160 * 8, // ge_cached * 8 SIGNATURE: 64 // ec_scalar * 2 }; this.valid_hex = function (hex) { return /[0-9a-fA-F]+/.test(hex); }; function hextobin(hex) { if (hex.length % 2 !== 0) throw "Hex string has invalid length!"; var res = new Uint8Array(hex.length / 2); for (var i = 0; i < hex.length / 2; ++i) { res[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); } return res; } this.hextobin = hextobin; function bintohex(bin) { var out = []; for (var i = 0; i < bin.length; ++i) { out.push(("0" + bin[i].toString(16)).slice(-2)); } return out.join(""); } this.sc_reduce = function (hex) { var input = hextobin(hex); if (input.length !== 64) { throw "Invalid input length"; } var mem = Module._malloc(64); Module.HEAPU8.set(input, mem); Module.ccall('sc_reduce', 'void', ['number'], [mem]); var output = Module.HEAPU8.subarray(mem, mem + 64); Module._free(mem); return bintohex(output); }; this.sc_reduce32 = function (hex) { var input = hextobin(hex); if (input.length !== 32) { throw "Invalid input length"; } var mem = Module._malloc(32); Module.HEAPU8.set(input, mem); Module.ccall('sc_reduce32', 'void', ['number'], [mem]); var output = Module.HEAPU8.subarray(mem, mem + 32); Module._free(mem); return bintohex(output); }; this.ge_scalarmult_base = function (hex) { var input = hextobin(hex); if (input.length !== 32) { throw "Invalid input length"; } var input_mem = Module._malloc(32); Module.HEAPU8.set(input, input_mem); var ge_p3 = Module._malloc(STRUCT_SIZES.GE_P3); Module.ccall('ge_scalarmult_base', 'void', ['number', 'number'], [ge_p3, input_mem]); var output = Module.HEAPU8.subarray(ge_p3, ge_p3 + STRUCT_SIZES.GE_P3); Module._free(input_mem); Module._free(ge_p3); return bintohex(output); }; this.ge_p3_tobytes = function (hex) { var input = hextobin(hex); if (input.length !== STRUCT_SIZES.GE_P3) { throw "Invalid input length"; } var ge_p3 = Module._malloc(STRUCT_SIZES.GE_P3); Module.HEAPU8.set(input, ge_p3); var out_mem = Module._malloc(32); Module.ccall('ge_p3_tobytes', 'void', ['number', 'number'], [out_mem, ge_p3]); var output = Module.HEAPU8.subarray(out_mem, out_mem + 32); Module._free(ge_p3); Module._free(out_mem); return bintohex(output); }; this.cn_fast_hash = function (input, inlen) { if (inlen === undefined || !inlen) { inlen = Math.floor(input.length / 2); } if (input.length !== inlen * 2) { console.log("Input length not equal to specified"); } var state = this.keccak(input, inlen, HASH_STATE_BYTES); return state.substr(0, HASH_SIZE * 2); }; this.encode_varint = function (i) { i = new JSBigInt(i); var out = ''; // While i >= b10000000 while (i.compare(0x80) >= 0) { // out.append i & b01111111 | b10000000 out += ("0" + ((i.lowVal() & 0x7f) | 0x80).toString(16)).slice(-2); i = i.divide(new JSBigInt(2).pow(7)); } out += ("0" + i.toJSValue().toString(16)).slice(-2); return out; }; this.pubkeys_to_string = function (spend, view) { var prefix = this.encode_varint(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); var data = prefix + spend + view; var checksum = this.cn_fast_hash(data); return cnBase58.encode(data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2)); }; this.address_checksum = function (spend, view) { var prefix = this.encode_varint(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); var data = prefix + spend + view; var checksum = this.cn_fast_hash(data); return checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2); }; // Generate keypair from seed this.generate_keys = function (seed) { if (seed.length !== 64) throw "Invalid input length!"; var sec = this.sc_reduce32(seed); var point = this.ge_scalarmult_base(sec); var pub = this.ge_p3_tobytes(point); return { 'sec': sec, 'pub': pub }; }; this.sec_key_to_pub = function (sec) { var point = this.ge_scalarmult_base(sec); var pub = this.ge_p3_tobytes(point); return pub; }; this.keccak = function (hex, inlen, outlen) { var input = hextobin(hex); if (input.length !== inlen) { throw "Invalid input length"; } if (outlen <= 0) { throw "Invalid output length"; } var input_mem = Module._malloc(inlen); Module.HEAPU8.set(input, input_mem); var out_mem = Module._malloc(outlen); Module._keccak(input_mem, inlen | 0, out_mem, outlen | 0); var output = Module.HEAPU8.subarray(out_mem, out_mem + outlen); Module._free(input_mem); Module._free(out_mem); return bintohex(output); }; this.structure_keys = function (spend, view) { var keys = {}; keys.spend = { 'sec': spend, 'pub': this.sec_key_to_pub(spend) }; keys.view = { 'sec': view, 'pub': this.sec_key_to_pub(view) }; keys.public_addr = this.pubkeys_to_string(keys.spend.pub, keys.view.pub); return keys; }; this.create_address = function (seed) { var keys = {}; var first; if (seed.length !== 64) { first = this.keccak(seed, seed.length / 2, 32); } else { first = seed; } keys.spend = this.generate_keys(first); var second = this.keccak(keys.spend.sec, 32, 32); keys.view = this.generate_keys(second); keys.public_addr = this.pubkeys_to_string(keys.spend.pub, keys.view.pub); return keys; }; this.create_address_if_prefix = function (seed, prefix) { var keys = {}; var first; if (seed.length !== 64) { first = this.keccak(seed, seed.length / 2, 32); } else { first = seed; } keys.spend = this.generate_keys(first); public_addr = this.pubkeys_to_string(keys.spend.pub, ""); if (public_addr.toUpperCase().slice(0, prefix.length) != prefix.toUpperCase()) return null; var second = this.keccak(keys.spend.sec, 32, 32); keys.view = this.generate_keys(second); keys.public_addr = this.pubkeys_to_string(keys.spend.pub, keys.view.pub); return keys; }; this.create_addr_prefix = function (seed) { var first; if (seed.length !== 64) { first = this.keccak(seed, seed.length / 2, 32); } else { first = seed; } var spend = this.generate_keys(first); var prefix = this.encode_varint(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); return cnBase58.encode(prefix + spend.pub).slice(0, 44); }; this.hash_to_ec = function (key) { if (key.length !== (KEY_SIZE * 2)) { throw "Invalid input length"; } var h_m = Module._malloc(HASH_SIZE); var point_m = Module._malloc(STRUCT_SIZES.GE_P2); var point2_m = Module._malloc(STRUCT_SIZES.GE_P1P1); var res_m = Module._malloc(STRUCT_SIZES.GE_P3); var hash = hextobin(this.cn_fast_hash(key, KEY_SIZE)); Module.HEAPU8.set(hash, h_m); Module.ccall("ge_fromfe_frombytes_vartime", "void", ["number", "number"], [point_m, h_m]); Module.ccall("ge_mul8", "void", ["number", "number"], [point2_m, point_m]); Module.ccall("ge_p1p1_to_p3", "void", ["number", "number"], [res_m, point2_m]); var res = Module.HEAPU8.subarray(res_m, res_m + STRUCT_SIZES.GE_P3); Module._free(h_m); Module._free(point_m); Module._free(point2_m); Module._free(res_m); return bintohex(res); }; this.verify_checksum = function (address) { var dec = cnBase58.decode(address); var checksum = dec.slice(dec.length - 8); var checksumHash = this.keccak(dec.slice(0, -8), (dec.length - 8) / 2, 32); var calculated = checksumHash.slice(0, 8); return checksum === calculated; }; this.decode_address = function (address) { var dec = cnBase58.decode(address); var expectedPrefix = this.encode_varint(CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); var prefix = dec.slice(0, expectedPrefix.length); if (prefix !== expectedPrefix) { throw "Invalid address prefix"; } dec = dec.slice(expectedPrefix.length); var spend = dec.slice(0, 64); var view = dec.slice(64, 128); var checksum = dec.slice(128, 128 + (ADDRESS_CHECKSUM_SIZE * 2)); var expectedChecksum = this.cn_fast_hash(prefix + spend + view).slice(0, ADDRESS_CHECKSUM_SIZE * 2); if (checksum !== expectedChecksum) { throw "Invalid checksum"; } return { spend: spend, view: view }; }; // Generate a 256-bit crypto random this.rand_32 = function () { return mn_random(256); }; // Generate a 128-bit crypto random this.rand_16 = function () { return mn_random(128); }; this.random_keypair = function () { return this.generate_keys(this.rand_32()); }; this.generate_key_derivation = function (pub, sec) { if (pub.length !== 64 || sec.length !== 64) { throw "Invalid input length"; } var pub_b = hextobin(pub); var sec_b = hextobin(sec); var pub_m = Module._malloc(KEY_SIZE); Module.HEAPU8.set(pub_b, pub_m); var sec_m = Module._malloc(KEY_SIZE); Module.HEAPU8.set(sec_b, sec_m); var ge_p3_m = Module._malloc(STRUCT_SIZES.GE_P3); var ge_p2_m = Module._malloc(STRUCT_SIZES.GE_P2); var ge_p1p1_m = Module._malloc(STRUCT_SIZES.GE_P1P1); if (Module.ccall("ge_frombytes_vartime", "bool", ["number", "number"], [ge_p3_m, pub_m]) !== 0) { throw "ge_frombytes_vartime returned non-zero error code"; } Module.ccall("ge_scalarmult", "void", ["number", "number", "number"], [ge_p2_m, sec_m, ge_p3_m]); Module.ccall("ge_mul8", "void", ["number", "number"], [ge_p1p1_m, ge_p2_m]); Module.ccall("ge_p1p1_to_p2", "void", ["number", "number"], [ge_p2_m, ge_p1p1_m]); var derivation_m = Module._malloc(KEY_SIZE); Module.ccall("ge_tobytes", "void", ["number", "number"], [derivation_m, ge_p2_m]); var res = Module.HEAPU8.subarray(derivation_m, derivation_m + KEY_SIZE); Module._free(pub_m); Module._free(sec_m); Module._free(ge_p3_m); Module._free(ge_p2_m); Module._free(ge_p1p1_m); Module._free(derivation_m); return bintohex(res); }; this.hash_to_scalar = function (buf) { var hash = this.cn_fast_hash(buf); var scalar = this.sc_reduce32(hash); return scalar; }; this.derivation_to_scalar = function (derivation, output_index) { var buf = ""; if (derivation.length !== (STRUCT_SIZES.EC_POINT * 2)) { throw "Invalid derivation length!"; } buf += derivation; var enc = encode_varint(output_index); if (enc.length > 10 * 2) { throw "output_index didn't fit in 64-bit varint"; } buf += enc; return this.hash_to_scalar(buf); }; this.derive_public_key = function (derivation, out_index, pub) { if (derivation.length !== 64 || pub.length !== 64) { throw "Invalid input length!"; } var derivation_m = Module._malloc(KEY_SIZE); var derivation_b = hextobin(derivation); Module.HEAPU8.set(derivation_b, derivation_m); var base_m = Module._malloc(KEY_SIZE); var base_b = hextobin(pub); Module.HEAPU8.set(base_b, base_m); var point1_m = Module._malloc(STRUCT_SIZES.GE_P3); var point2_m = Module._malloc(STRUCT_SIZES.GE_P3); var point3_m = Module._malloc(STRUCT_SIZES.GE_CACHED); var point4_m = Module._malloc(STRUCT_SIZES.GE_P1P1); var point5_m = Module._malloc(STRUCT_SIZES.GE_P2); var derived_key_m = Module._malloc(KEY_SIZE); if (Module.ccall("ge_frombytes_vartime", "bool", ["number", "number"], [point1_m, base_m]) !== 0) { throw "ge_frombytes_vartime returned non-zero error code"; } var scalar_m = Module._malloc(STRUCT_SIZES.EC_SCALAR); var scalar_b = hextobin(this.derivation_to_scalar( bintohex(Module.HEAPU8.subarray(derivation_m, derivation_m + STRUCT_SIZES.EC_POINT)), out_index)); Module.HEAPU8.set(scalar_b, scalar_m); Module.ccall("ge_scalarmult_base", "void", ["number", "number"], [point2_m, scalar_m]); Module.ccall("ge_p3_to_cached", "void", ["number", "number"], [point3_m, point2_m]); Module.ccall("ge_add", "void", ["number", "number", "number"], [point4_m, point1_m, point3_m]); Module.ccall("ge_p1p1_to_p2", "void", ["number", "number"], [point5_m, point4_m]); Module.ccall("ge_tobytes", "void", ["number", "number"], [derived_key_m, point5_m]); var res = Module.HEAPU8.subarray(derived_key_m, derived_key_m + KEY_SIZE); Module._free(derivation_m); Module._free(base_m); Module._free(scalar_m); Module._free(point1_m); Module._free(point2_m); Module._free(point3_m); Module._free(point4_m); Module._free(point5_m); Module._free(derived_key_m); return bintohex(res); }; this.derive_secret_key = function (derivation, out_index, sec) { if (derivation.length !== 64 || sec.length !== 64) { throw "Invalid input length!"; } var scalar_m = Module._malloc(STRUCT_SIZES.EC_SCALAR); var scalar_b = hextobin(this.derivation_to_scalar(derivation, out_index)); Module.HEAPU8.set(scalar_b, scalar_m); var base_m = Module._malloc(KEY_SIZE); Module.HEAPU8.set(hextobin(sec), base_m); var derived_m = Module._malloc(STRUCT_SIZES.EC_POINT); Module.ccall("sc_add", "void", ["number", "number", "number"], [derived_m, base_m, scalar_m]); var res = Module.HEAPU8.subarray(derived_m, derived_m + STRUCT_SIZES.EC_POINT); Module._free(scalar_m); Module._free(base_m); Module._free(derived_m); return bintohex(res); }; // Random 32-byte ec scalar this.random_scalar = function () { var rand = this.sc_reduce(mn_random(64 * 8)); return rand.slice(0, STRUCT_SIZES.EC_SCALAR * 2); }; this.valid_keys = function (view_pub, view_sec, spend_pub, spend_sec) { var expected_view_pub = this.sec_key_to_pub(view_sec); var expected_spend_pub = this.sec_key_to_pub(spend_sec); return (expected_spend_pub === spend_pub) && (expected_view_pub === view_pub); }; } module.exports = new CnUtil();