raiden-ts
Version:
Raiden Light Client Typescript/Javascript SDK
393 lines • 18.5 kB
JavaScript
;
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