UNPKG

raiden-ts

Version:

Raiden Light Client Typescript/Javascript SDK

393 lines 18.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseMessage = exports.isMessageReceivedOfType = exports.signMessage = exports.decodeJsonMessage = exports.encodeJsonMessage = exports.getBalanceProofFromEnvelopeMessage = exports.getMessageSigner = exports.isSigned = exports.packMessage = exports.createMessageHash = exports.createBalanceHash = exports.MessageTypeId = void 0; const bytes_1 = require("@ethersproject/bytes"); const constants_1 = require("@ethersproject/constants"); const keccak256_1 = require("@ethersproject/keccak256"); const rlp_1 = require("@ethersproject/rlp"); const strings_1 = require("@ethersproject/strings"); const wallet_1 = require("@ethersproject/wallet"); const json_canonicalize_1 = require("json-canonicalize"); const loglevel_1 = __importDefault(require("loglevel")); const constants_2 = require("../constants"); const utils_1 = require("../transport/utils"); const utils_2 = require("../utils"); const data_1 = require("../utils/data"); const error_1 = require("../utils/error"); const types_1 = require("../utils/types"); const actions_1 = require("./actions"); const types_2 = require("./types"); const CMDIDs = { [types_2.MessageType.DELIVERED]: 12, [types_2.MessageType.PROCESSED]: 0, [types_2.MessageType.SECRET_REQUEST]: 3, [types_2.MessageType.SECRET_REVEAL]: 11, [types_2.MessageType.LOCKED_TRANSFER]: 7, [types_2.MessageType.UNLOCK]: 4, [types_2.MessageType.LOCK_EXPIRED]: 13, [types_2.MessageType.WITHDRAW_REQUEST]: 15, [types_2.MessageType.WITHDRAW_CONFIRMATION]: 16, [types_2.MessageType.WITHDRAW_EXPIRED]: 17, [types_2.MessageType.PFS_CAPACITY_UPDATE]: -1, [types_2.MessageType.PFS_FEE_UPDATE]: -1, [types_2.MessageType.MONITOR_REQUEST]: -1, }; // raiden_contracts.constants.MessageTypeId var MessageTypeId; (function (MessageTypeId) { MessageTypeId[MessageTypeId["BALANCE_PROOF"] = 1] = "BALANCE_PROOF"; MessageTypeId[MessageTypeId["BALANCE_PROOF_UPDATE"] = 2] = "BALANCE_PROOF_UPDATE"; MessageTypeId[MessageTypeId["WITHDRAW"] = 3] = "WITHDRAW"; MessageTypeId[MessageTypeId["COOP_SETTLE"] = 4] = "COOP_SETTLE"; MessageTypeId[MessageTypeId["IOU"] = 5] = "IOU"; MessageTypeId[MessageTypeId["MS_REWARD"] = 6] = "MS_REWARD"; })(MessageTypeId = exports.MessageTypeId || (exports.MessageTypeId = {})); /** * Create the hash of Metadata structure. * * @param metadata - The LockedTransfer metadata * @returns Hash of the metadata. */ function createMetadataHash(metadata) { return (0, keccak256_1.keccak256)((0, strings_1.toUtf8Bytes)((0, json_canonicalize_1.canonicalize)(metadata))); } /** * Returns a balance_hash from transferred&locked amounts & locksroot * * @param bp - BalanceProof-like object * @param bp.transferredAmount - balanceProof's transferredAmount * @param bp.lockedAmount - balanceProof's lockedAmount * @param bp.locksroot - balanceProof's locksroot * @returns Hash of the balance */ function createBalanceHash({ transferredAmount, lockedAmount, locksroot, }) { let hash = constants_1.HashZero; if (!transferredAmount.isZero() || !lockedAmount.isZero() || (locksroot !== constants_1.HashZero && locksroot !== constants_2.LocksrootZero)) hash = (0, keccak256_1.keccak256)((0, bytes_1.concat)([ (0, data_1.encode)(transferredAmount, 32), (0, data_1.encode)(lockedAmount, 32), (0, data_1.encode)(locksroot, 32), ])); return hash; } exports.createBalanceHash = createBalanceHash; /** * Create the messageHash/additionalHash for a given EnvelopeMessage * * @param message - EnvelopeMessage to pack * @returns Hash of the message pack */ function createMessageHash(message) { let hash; switch (message.type) { case types_2.MessageType.LOCKED_TRANSFER: // hash of packed representation of the whole message hash = (0, keccak256_1.keccak256)((0, bytes_1.concat)([ (0, data_1.encode)(CMDIDs[message.type], 1), (0, data_1.encode)(message.message_identifier, 8), (0, data_1.encode)(message.payment_identifier, 8), (0, data_1.encode)(message.lock.expiration, 32), (0, data_1.encode)(message.token, 20), (0, data_1.encode)(message.recipient, 20), (0, data_1.encode)(message.target, 20), (0, data_1.encode)(message.initiator, 20), (0, data_1.encode)(message.lock.secrethash, 32), (0, data_1.encode)(message.lock.amount, 32), createMetadataHash(message.metadata), ])); break; case types_2.MessageType.UNLOCK: hash = (0, keccak256_1.keccak256)((0, bytes_1.concat)([ (0, data_1.encode)(CMDIDs[message.type], 1), (0, data_1.encode)(message.message_identifier, 8), (0, data_1.encode)(message.payment_identifier, 8), (0, data_1.encode)(message.secret, 32), ])); break; case types_2.MessageType.LOCK_EXPIRED: hash = (0, keccak256_1.keccak256)((0, bytes_1.concat)([ (0, data_1.encode)(CMDIDs[message.type], 1), (0, data_1.encode)(message.message_identifier, 8), (0, data_1.encode)(message.recipient, 20), (0, data_1.encode)(message.secrethash, 32), ])); break; } return hash; } exports.createMessageHash = createMessageHash; /** * Pack a message in a hex-string format, **without** signature * This packed hex-byte-array can then be used for signing. * On Raiden python client, this is the output of `_data_to_sign` method of the messages, as the * actual packed encoding was once used for binary transport protocols, but nowadays is used only * for generating data to be signed, which is the purpose of our implementation. * * @param message - Message to be packed * @returns HexBytes hex-encoded string data representing message in binary format */ function packMessage(message) { switch (message.type) { case types_2.MessageType.DELIVERED: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(CMDIDs[message.type], 1), (0, data_1.encode)(0, 3), (0, data_1.encode)(message.delivered_message_identifier, 8), ])); case types_2.MessageType.PROCESSED: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(CMDIDs[message.type], 1), (0, data_1.encode)(0, 3), (0, data_1.encode)(message.message_identifier, 8), ])); case types_2.MessageType.LOCKED_TRANSFER: case types_2.MessageType.UNLOCK: case types_2.MessageType.LOCK_EXPIRED: { const additionalHash = createMessageHash(message), balanceHash = createBalanceHash({ transferredAmount: message.transferred_amount, lockedAmount: message.locked_amount, locksroot: message.locksroot, }); return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(message.token_network_address, 20), (0, data_1.encode)(message.chain_id, 32), (0, data_1.encode)(MessageTypeId.BALANCE_PROOF, 32), (0, data_1.encode)(message.channel_identifier, 32), (0, data_1.encode)(balanceHash, 32), (0, data_1.encode)(message.nonce, 32), (0, data_1.encode)(additionalHash, 32), ])); } case types_2.MessageType.SECRET_REQUEST: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(CMDIDs[message.type], 1), (0, data_1.encode)(0, 3), (0, data_1.encode)(message.message_identifier, 8), (0, data_1.encode)(message.payment_identifier, 8), (0, data_1.encode)(message.secrethash, 32), (0, data_1.encode)(message.amount, 32), (0, data_1.encode)(message.expiration, 32), ])); case types_2.MessageType.SECRET_REVEAL: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(CMDIDs[message.type], 1), (0, data_1.encode)(0, 3), (0, data_1.encode)(message.message_identifier, 8), (0, data_1.encode)(message.secret, 32), ])); case types_2.MessageType.WITHDRAW_REQUEST: case types_2.MessageType.WITHDRAW_CONFIRMATION: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(message.token_network_address, 20), (0, data_1.encode)(message.chain_id, 32), (0, data_1.encode)(MessageTypeId.WITHDRAW, 32), (0, data_1.encode)(message.channel_identifier, 32), (0, data_1.encode)(message.participant, 20), (0, data_1.encode)(message.total_withdraw, 32), (0, data_1.encode)(message.expiration, 32), ])); case types_2.MessageType.WITHDRAW_EXPIRED: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(CMDIDs[message.type], 1), (0, data_1.encode)(0, 3), (0, data_1.encode)(message.nonce, 32), (0, data_1.encode)(message.message_identifier, 8), (0, data_1.encode)(message.token_network_address, 20), (0, data_1.encode)(message.chain_id, 32), (0, data_1.encode)(MessageTypeId.WITHDRAW, 32), (0, data_1.encode)(message.channel_identifier, 32), (0, data_1.encode)(message.participant, 20), (0, data_1.encode)(message.total_withdraw, 32), (0, data_1.encode)(message.expiration, 32), ])); case types_2.MessageType.PFS_CAPACITY_UPDATE: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(message.canonical_identifier.chain_identifier, 32), (0, data_1.encode)(message.canonical_identifier.token_network_address, 20), (0, data_1.encode)(message.canonical_identifier.channel_identifier, 32), (0, data_1.encode)(message.updating_participant, 20), (0, data_1.encode)(message.other_participant, 20), (0, data_1.encode)(message.updating_nonce, 8), (0, data_1.encode)(message.other_nonce, 8), (0, data_1.encode)(message.updating_capacity, 32), (0, data_1.encode)(message.other_capacity, 32), (0, data_1.encode)(message.reveal_timeout, 32), ])); case types_2.MessageType.PFS_FEE_UPDATE: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(message.canonical_identifier.chain_identifier, 32), (0, data_1.encode)(message.canonical_identifier.token_network_address, 20), (0, data_1.encode)(message.canonical_identifier.channel_identifier, 32), (0, data_1.encode)(message.updating_participant, 20), (0, data_1.encode)(message.fee_schedule.cap_fees, 1), (0, data_1.encode)(message.fee_schedule.flat, 32), (0, data_1.encode)(message.fee_schedule.proportional, 32), (0, rlp_1.encode)(message.fee_schedule.imbalance_penalty ? message.fee_schedule.imbalance_penalty.map((point) => // RLP integer 0 must be encoded as the empty bytestring point.map((p) => (p.isZero() ? '0x' : p.toHexString()))) : '0x'), (0, strings_1.toUtf8Bytes)(message.timestamp), ])); // variable size of fee_schedule.imbalance_penalty rlpEncoding, when not null case types_2.MessageType.MONITOR_REQUEST: return (0, bytes_1.hexlify)((0, bytes_1.concat)([ (0, data_1.encode)(message.monitoring_service_contract_address, 20), (0, data_1.encode)(message.balance_proof.chain_id, 32), (0, data_1.encode)(MessageTypeId.MS_REWARD, 32), (0, data_1.encode)(message.balance_proof.token_network_address, 20), (0, data_1.encode)(message.non_closing_participant, 20), (0, data_1.encode)(message.non_closing_signature, 65), (0, data_1.encode)(message.reward_amount, 32), ])); } } exports.packMessage = packMessage; /** * Typeguard to check if a message contains a valid signature * * @param message - May or may not be a signed message * @returns Boolean if message is signed */ function isSigned(message) { return types_1.Signature.is(message.signature); } exports.isSigned = isSigned; /** * Requires a signed message and returns its signer address * * @param message - Signed message to retrieve signer address * @param caps - Sender's capabilities which may change how signature is verified * @returns Address which signed message */ function getMessageSigner(message, caps) { // if !caps.immutableMetadata, partner doesn't sign the same thing they send, but the decoded // version of it (checksummed addresses) if (!(0, utils_1.getCap)(caps, constants_2.Capabilities.IMMUTABLE_METADATA) && 'metadata' in message) message = { ...message, metadata: (0, types_1.decode)(types_2.Metadata, message.metadata) }; return (0, wallet_1.verifyMessage)((0, bytes_1.arrayify)(packMessage(message)), message.signature); } exports.getMessageSigner = getMessageSigner; /** * Get the signed BalanceProof associated with an EnvelopeMessage * * @param message - Signed EnvelopeMessage * @returns Signed BalanceProof object for message */ function getBalanceProofFromEnvelopeMessage(message) { return { chainId: message.chain_id, tokenNetworkAddress: message.token_network_address, channelId: message.channel_identifier, nonce: message.nonce, transferredAmount: message.transferred_amount, lockedAmount: message.locked_amount, locksroot: message.locksroot, additionalHash: createMessageHash(message), signature: message.signature, }; } exports.getBalanceProofFromEnvelopeMessage = getBalanceProofFromEnvelopeMessage; /** * Encode a Message as a JSON string * Uses io-ts codec to encode BigNumbers as JSON 'string' type, as Raiden * * @param message - Message object to be serialized * @returns JSON string */ function encodeJsonMessage(message) { if ('signature' in message) return (0, data_1.jsonStringify)((0, types_1.Signed)(types_2.Message).encode(message)); return (0, data_1.jsonStringify)(types_2.Message.encode(message)); } exports.encodeJsonMessage = encodeJsonMessage; /** * Try to decode text as a Message, using io-ts codec to decode BigNumbers * Throws if can't decode, or message is invalid regarding any of the encoded constraints * * @param text - JSON string to try to decode * @returns Message object */ function decodeJsonMessage(text) { const parsed = (0, data_1.jsonParse)(text); (0, utils_2.assert)(parsed && typeof parsed === 'object' && 'type' in parsed && Object.values(types_2.MessageType).some((t) => t === parsed['type']), `Invalid message type: ${parsed?.['type']}`); if ('signature' in parsed) return (0, types_1.decode)((0, types_1.Signed)(types_2.Message), parsed); return (0, types_1.decode)(types_2.Message, parsed); } exports.decodeJsonMessage = decodeJsonMessage; /** * Pack message and request signer to sign it, and returns signed message * * @param signer - Signer instance * @param message - Unsigned message to pack and sign * @param opts - Options * @param opts.log - Logger instance * @returns Promise to signed message */ async function signMessage(signer, message, { log } = { log: loglevel_1.default }) { if (isSigned(message)) return message; log.debug(`Signing message "${message.type}"`, message); const signature = (await signer.signMessage((0, bytes_1.arrayify)(packMessage(message)))); return { ...message, signature }; } exports.signMessage = signMessage; /** * Typeguard to ensure an action is a messageReceived of any of a set of Message types * * @param messageCodecs - Message codec to test action.payload.message against * @returns Typeguard intersecting messageReceived action and payload.message schemas */ function isMessageReceivedOfType(messageCodecs) { /** * Typeguard function * * @param action - Some action to guard to be a messageReceved * @returns Whether or not action is a messageReceved of given type */ return (action) => actions_1.messageReceived.is(action) && (Array.isArray(messageCodecs) ? messageCodecs.some((c) => c.is(action.payload.message)) : messageCodecs.is(action.payload.message)); } exports.isMessageReceivedOfType = isMessageReceivedOfType; /** * Parse a received message into either a Message or Signed<Message> * If Signed, the signer must match the sender's address. * Errors are logged and undefined returned * * @param line - String to be parsed as a single message * @param sender - Sender's presence * @param deps - Dependencies * @param deps.log - Logger instance * @returns Validated Signed or unsigned Message, or undefined */ function parseMessage(line, sender, { log }) { if (typeof line !== 'string') return; try { const message = decodeJsonMessage(line); // if Signed, accept only if signature matches sender address if ('signature' in message) { const signer = getMessageSigner(message, sender.payload.caps); (0, utils_2.assert)(signer === sender.meta.address, [ error_1.ErrorCodes.TRNS_MESSAGE_SIGNATURE_MISMATCH, { sender: sender.meta.address, signer }, ]); } return message; } catch (err) { log.warn(`Could not decode message: ${line}: ${err}`); } } exports.parseMessage = parseMessage; //# sourceMappingURL=utils.js.map