UNPKG

ardorjs

Version:

Local transaction signing for ardor transactions, and ardor address creations

497 lines (445 loc) 15.1 kB
import curve25519 from "../util/curve25519.js"; import curve25519_ from "../util/curve25519_.js"; import NxtAddress from "../util/nxtaddress.js"; import helpers from "./helpers"; import BigInteger from "jsbn"; import * as pako from "pako"; import cryptoJs from "crypto-js"; import hex from "crypto-js/enc-hex"; import { Buffer } from "buffer"; function rsConvert(address) { var addr = new NxtAddress(); addr.set(address); return { account: addr.account_id(), accountRS: addr.toString(), }; } function secretPhraseToPublicKey(secretPhrase, asByteArray) { var hash = helpers.hexStringToByteArray( helpers.simpleHash(secretPhrase, "hex") ); var pubKey = curve25519.keygen(hash).p; if (asByteArray) { return pubKey; } return helpers.byteArrayToHexString(pubKey); } function publicKeyToAccountId(publicKey, numeric) { var arr = helpers.hexStringToByteArray(publicKey); var account = helpers.simpleHash(arr, "hex"); var slice = helpers.hexStringToByteArray(account).slice(0, 8); var accountId = helpers.byteArrayToBigInteger(slice).toString(); if (numeric) { return accountId; } var address = new NxtAddress(); if (!address.set(accountId)) { return ""; } return address.toString(); } function secretPhraseToAccountId(secretPhrase, numeric) { var pubKey = secretPhraseToPublicKey(secretPhrase); return publicKeyToAccountId(pubKey, numeric); } function signTransactionBytes(data, secretPhrase) { var unsignedBytes = helpers.hexStringToByteArray(data); var sig = signBytes(unsignedBytes, secretPhrase); const VERSION_POSITION = 6 const SIGNATURE_POSITION = 69; const SIGNATURE_POSITION_V2 = 45; var sigPos = 2 * (unsignedBytes[VERSION_POSITION] < 2 ? SIGNATURE_POSITION : SIGNATURE_POSITION_V2); var sigLen = 2 * 64; var signature = helpers.byteArrayToHexString(sig); var signed = data.substr(0, sigPos) + signature + data.substr(sigPos + sigLen); return signed; } function signBytes(message, secretPhrase) { var messageBytes = message; var secretPhraseBytes = helpers.stringToByteArray(secretPhrase); var digest = helpers.simpleHash(secretPhraseBytes); var s = curve25519.keygen(digest).s; var m = helpers.simpleHash(messageBytes); var mBuf = Buffer.from(m); var sBuf = Buffer.from(s); /** Old crypto lib code */ // var hash = crypto.createHash("sha256"); // hash.update(mBuf); // hash.update(sBuf); // var x = hash.digest(); /** */ let hash1 = cryptoJs.lib.WordArray.create(mBuf); let hash2 = cryptoJs.lib.WordArray.create(sBuf); var beforeBuffer = cryptoJs.SHA256(hash1.concat(hash2)); var x = Buffer.from(beforeBuffer.toString(hex), "hex"); var y = curve25519.keygen(x).p; /** Old crypto lib code */ // hash = crypto.createHash("sha256"); // var yBuf = Buffer.from(y); // hash.update(mBuf); // hash.update(yBuf); // var h = helpers.hexStringToByteArray(hash.digest("hex")); /** */ let hash3 = cryptoJs.lib.WordArray.create(mBuf); let hash4 = cryptoJs.lib.WordArray.create(Buffer.from(y)); var h = helpers.hexStringToByteArray( cryptoJs.SHA256(hash3.concat(hash4)).toString(hex) ); var v = curve25519.sign(h, x, s); return v.concat(h); } function verifyTransactionBytes(byteArray, requestType, data, publicKey) { byteArray = helpers.hexStringToByteArray(byteArray); var transaction = {}; var pos = 0; transaction.chain = String(helpers.byteArrayToSignedInt32(byteArray, pos)); pos += 4; transaction.type = byteArray[pos++]; if (transaction.type >= 128) { transaction.type -= 256; } transaction.subtype = byteArray[pos++]; transaction.version = byteArray[pos++]; transaction.timestamp = String( helpers.byteArrayToSignedInt32(byteArray, pos) ); pos += 4; transaction.deadline = String(helpers.byteArrayToSignedShort(byteArray, pos)); pos += 2; if (transaction.version < 2) { transaction.publicKey = helpers.byteArrayToHexString(byteArray.slice(pos, pos + 32)); pos += 32; } else { transaction.sender = String(helpers.byteArrayToBigInteger(byteArray, pos)); pos += 8; } transaction.recipient = String(helpers.byteArrayToBigInteger(byteArray, pos)); pos += 8; transaction.amountNQT = String(helpers.byteArrayToBigInteger(byteArray, pos)); pos += 8; transaction.feeNQT = String(helpers.byteArrayToBigInteger(byteArray, pos)); pos += 8; transaction.signature = byteArray.slice(pos, pos + 64); pos += 64; transaction.ecBlockHeight = String( helpers.byteArrayToSignedInt32(byteArray, pos) ); pos += 4; transaction.ecBlockId = String(helpers.byteArrayToBigInteger(byteArray, pos)); pos += 8; transaction.flags = String(helpers.byteArrayToSignedInt32(byteArray, pos)); pos += 4; if (transaction.version < 2) { if (transaction.publicKey !== publicKey) { return false; } } else { var accountId = publicKeyToAccountId(publicKey, true); if (transaction.sender != accountId) { return false; } } if (data.deadline) { // Only check deadline if it was provided if (Number(transaction.deadline) !== Number(data.deadline)) { return false; } } // Handle different prefix by ignoring everything before the first - if ( !( (data.recipient === undefined || data.recipient == "") && transaction.recipient == "0" ) ) { var transaction_raddress = rsConvert(transaction.recipient)["accountRS"]; var data_raddress = rsConvert(data.recipient)['accountRS']; if ( transaction_raddress.substring(transaction_raddress.indexOf("-")) !== data_raddress.substring(data_raddress.indexOf("-")) ) { return false; } } if ( Number(transaction.amountNQT) !== Number(data.amountNQT) && !(requestType === "exchangeCoins" && transaction.amountNQT === "0") ) { return false; } // if ("referencedTransactionFullHash" in data) { // if (transaction.referencedTransactionFullHash !== data.referencedTransactionFullHash) { // return false; // } // } else if (transaction.referencedTransactionFullHash && transaction.referencedTransactionFullHash !== "") { // return false; // } //has empty attachment, so no attachmentVersion byte... if (!(requestType == "sendMoney" || requestType == "sendMessage")) { pos++; } //return NRS.verifyTransactionTypes(byteArray, transaction, requestType, data, pos, attachment); //Missing function to check transaction type return true; } function generateToken(message, secretPhrase, isTestnet) { var messageBytes = helpers.getUtf8Bytes(message); var pubKeyBytes = helpers.hexStringToByteArray( secretPhraseToPublicKey(secretPhrase) ); var token = pubKeyBytes; var tsb = []; var ts = helpers.toEpochTime(undefined, isTestnet); tsb[0] = ts & 0xff; tsb[1] = (ts >> 8) & 0xff; tsb[2] = (ts >> 16) & 0xff; tsb[3] = (ts >> 24) & 0xff; messageBytes = messageBytes.concat(pubKeyBytes, tsb); token = token.concat(tsb, signBytes(messageBytes, secretPhrase)); var buf = ""; for (var ptr = 0; ptr < 100; ptr += 5) { var nbr = []; nbr[0] = token[ptr] & 0xff; nbr[1] = token[ptr + 1] & 0xff; nbr[2] = token[ptr + 2] & 0xff; nbr[3] = token[ptr + 3] & 0xff; nbr[4] = token[ptr + 4] & 0xff; var number = byteArrayToBigInteger(nbr); if (number < 32) { buf += "0000000"; } else if (number < 1024) { buf += "000000"; } else if (number < 32768) { buf += "00000"; } else if (number < 1048576) { buf += "0000"; } else if (number < 33554432) { buf += "000"; } else if (number < 1073741824) { buf += "00"; } else if (number < 34359738368) { buf += "0"; } buf += number.toString(32); } return buf; } function decryptNote(message, options, secretPhrase) { options.privateKey = helpers.hexStringToByteArray( getPrivateKey(secretPhrase) ); if (!options.publicKey) { options.publicKey = helpers.hexStringToByteArray( secretPhraseToPublicKey(secretPhrase) ); } else { //Added for decypt message because if public key was provided, it was the hex string options.publicKey = helpers.hexStringToByteArray(options.publicKey); } if (options.nonce) { options.nonce = helpers.hexStringToByteArray(options.nonce); } return decryptData(helpers.hexStringToByteArray(message), options); } function encryptMessage( text, senderSecretPhrase, recipientPublicKey, isMessageToSelf ) { var encrypted = encryptNote( text, { publicKey: recipientPublicKey, }, senderSecretPhrase ); if (isMessageToSelf) { return { encryptToSelfMessageData: encrypted.message, encryptToSelfMessageNonce: encrypted.nonce, messageToEncryptToSelfIsText: "true", }; } else { return { encryptedMessageData: encrypted.message, encryptedMessageNonce: encrypted.nonce, messageToEncryptIsText: "true", }; } } function encryptNote(message, options, secretPhrase) { options.privateKey = helpers.hexStringToByteArray( getPrivateKey(secretPhrase) ); if (!options.publicKey) { options.publicKey = helpers.hexStringToByteArray( secretPhraseToPublicKey(secretPhrase) ); } else { //Added for encrypt message because if public key was provided, it was the hex string options.publicKey = helpers.hexStringToByteArray(options.publicKey); } let encrypted = encryptData(helpers.stringToByteArray(message), options); return { message: helpers.byteArrayToHexString(encrypted.data), nonce: helpers.byteArrayToHexString(encrypted.nonce), }; } //Local Functions function decryptData(data, options) { if (!options.sharedKey) { options.sharedKey = getSharedSecret(options.privateKey, options.publicKey); } var result = aesDecrypt(data, options); var binData = new Uint8Array(result.decrypted); if (!(options.isCompressed === false)) { binData = pako.inflate(binData); } var message; if (!(options.isText === false)) { message = helpers.byteArrayToString(binData); } else { message = helpers.byteArrayToHexString(binData); } return { message: message, sharedKey: helpers.byteArrayToHexString(result.sharedKey), }; } function encryptData(plaintext, options) { options.nonce = getRandomBytes(32); if (!options.sharedKey) { options.sharedKey = getSharedSecret(options.privateKey, options.publicKey); } var compressedPlaintext = pako.gzip(new Uint8Array(plaintext)); var data = aesEncrypt(compressedPlaintext, options); return { nonce: options.nonce, data: data, }; } function getRandomBytes(length) { if (!window.crypto && !window.msCrypto && !crypto) { throw { errorCode: -1, message: $.t("error_encryption_browser_support"), }; } var bytes = new Uint8Array(length); if (window.crypto) { //noinspection JSUnresolvedFunction window.crypto.getRandomValues(bytes); } else if (window.msCrypto) { //noinspection JSUnresolvedFunction window.msCrypto.getRandomValues(bytes); } else { bytes = cryptoJs.lib.WordArray.random(length); //bytes = crypto.randomBytes(length); } return bytes; } function getSharedSecret(key1, key2) { return helpers.shortArrayToByteArray( curve25519_( helpers.byteArrayToShortArray(key1), helpers.byteArrayToShortArray(key2), null ) ); } function aesDecrypt(ivCiphertext, options) { if (ivCiphertext.length < 16 || ivCiphertext.length % 16 != 0) { throw { name: "invalid ciphertext", }; } var iv = helpers.byteArrayToWordArray(ivCiphertext.slice(0, 16)); var ciphertext = helpers.byteArrayToWordArray(ivCiphertext.slice(16)); // shared key is use for two different purposes here // (1) if nonce exists, shared key represents the shared secret between the private and public keys // (2) if nonce does not exists, shared key is the specific key needed for decryption already xored // with the nonce and hashed var sharedKey; if (!options.sharedKey) { sharedKey = getSharedSecret(options.privateKey, options.publicKey); } else { sharedKey = options.sharedKey.slice(0); //clone } var key; if (options.nonce) { for (var i = 0; i < 32; i++) { sharedKey[i] ^= options.nonce[i]; } key = cryptoJs.SHA256(helpers.byteArrayToWordArray(sharedKey)); } else { key = helpers.byteArrayToWordArray(sharedKey); } var encrypted = cryptoJs.lib.CipherParams.create({ ciphertext: ciphertext, iv: iv, key: key, }); var decrypted = cryptoJs.AES.decrypt(encrypted, key, { iv: iv, }); return { decrypted: helpers.wordArrayToByteArray(decrypted, true), sharedKey: helpers.wordArrayToByteArray(key, true), }; } function aesEncrypt(plaintext, options) { var ivBytes = getRandomBytes(16); // CryptoJS likes WordArray parameters var text = helpers.byteArrayToWordArray(plaintext); var sharedKey; if (!options.sharedKey) { sharedKey = getSharedSecret(options.privateKey, options.publicKey); } else { sharedKey = options.sharedKey.slice(0); //clone } for (var i = 0; i < 32; i++) { sharedKey[i] ^= options.nonce[i]; } var key = cryptoJs.SHA256(helpers.byteArrayToWordArray(sharedKey)); var encrypted = cryptoJs.AES.encrypt(text, key, { iv: helpers.byteArrayToWordArray(ivBytes), }); var ivOut = helpers.wordArrayToByteArray(encrypted.iv, true); var ciphertextOut = helpers.wordArrayToByteArray(encrypted.ciphertext, true); return ivOut.concat(ciphertextOut); } function getPrivateKey(secretPhrase) { var bytes = helpers.simpleHash(helpers.stringToByteArray(secretPhrase)); return helpers.shortArrayToHexString( curve25519_clamp(helpers.byteArrayToShortArray(bytes)) ); } function curve25519_clamp(curve) { curve[0] &= 0xfff8; curve[15] &= 0x7fff; curve[15] |= 0x4000; return curve; } function byteArrayToBigInteger(byteArray) { var value = new BigInteger("0", 10); for (var i = byteArray.length - 1; i >= 0; i--) { value = value .multiply(new BigInteger("256", 10)) .add(new BigInteger(byteArray[i].toString(10), 10)); } return value; } export default { rsConvert: rsConvert, secretPhraseToPublicKey: secretPhraseToPublicKey, publicKeyToAccountId: publicKeyToAccountId, secretPhraseToAccountId: secretPhraseToAccountId, signTransactionBytes: signTransactionBytes, signBytes: signBytes, verifyTransactionBytes: verifyTransactionBytes, generateToken: generateToken, decryptNote: decryptNote, encryptNote: encryptNote, encryptMessage: encryptMessage, };