UNPKG

raiden-ts

Version:

Raiden Light Client Typescript/Javascript SDK

391 lines 16.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.searchValidViaAddress = exports.searchValidMetadata = exports.decryptSecretFromMetadata = exports.metadataFromPaths = exports.clearMetadataRoute = exports.getTransfer = exports.findBalanceProofMatchingBalanceHash$ = exports.raidenTransfer = exports.transferCompleted = exports.transferStatus = exports.transferKeyToMeta = exports.transferKey = exports.makeMessageId = exports.makePaymentId = exports.makeSecret = exports.getSecrethash = exports.getLocksroot = void 0; const bignumber_1 = require("@ethersproject/bignumber"); const bytes_1 = require("@ethersproject/bytes"); const constants_1 = require("@ethersproject/constants"); const keccak256_1 = require("@ethersproject/keccak256"); const random_1 = require("@ethersproject/random"); const sha2_1 = require("@ethersproject/sha2"); const eciesjs_1 = require("eciesjs"); const t = __importStar(require("io-ts")); const isEmpty_1 = __importDefault(require("lodash/isEmpty")); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const types_1 = require("../channels/types"); const utils_1 = require("../channels/utils"); const constants_2 = require("../constants"); const types_2 = require("../messages/types"); const utils_2 = require("../messages/utils"); const utils_3 = require("../services/utils"); const utils_4 = require("../transport/utils"); const utils_5 = require("../utils"); const data_1 = require("../utils/data"); const types_3 = require("../utils/types"); const state_1 = require("./state"); /** * Get the locksroot of a given array of pending locks * On Alderaan, it's the keccak256 hash of the concatenation of the ordered locks data * * @param locks - Lock array to calculate the locksroot from * @returns hash of the locks array */ function getLocksroot(locks) { const encoded = []; for (const lock of locks) encoded.push((0, data_1.encode)(lock.expiration, 32), (0, data_1.encode)(lock.amount, 32), lock.secrethash); return (0, keccak256_1.keccak256)((0, bytes_1.concat)(encoded)); } exports.getLocksroot = getLocksroot; /** * Return the secrethash of a given secret * On Alderaan, the sha256 hash is used for the secret. * * @param secret - Secret to get the hash from * @returns hash of the secret */ function getSecrethash(secret) { return (0, sha2_1.sha256)(secret); } exports.getSecrethash = getSecrethash; /** * Generates a random secret of given length, as an HexString<32> * * @param length - of the secret to generate * @returns HexString<32> */ function makeSecret(length = 32) { return (0, bytes_1.hexlify)((0, random_1.randomBytes)(length)); } exports.makeSecret = makeSecret; let lastPaymentId = 0; /** * Generates a unique 64bits payment identifier * * @returns Unique UInt<8> */ function makePaymentId() { return bignumber_1.BigNumber.from((lastPaymentId = Math.max(lastPaymentId + 1, Date.now()))); } exports.makePaymentId = makePaymentId; let lastMsgId = 0; /** * Generates a unique 64bits message identifier * * @returns Unique UInt<8> */ function makeMessageId() { return bignumber_1.BigNumber.from((lastMsgId = Math.max(lastMsgId + 1, Date.now()))); } exports.makeMessageId = makeMessageId; /** * Get a unique key for a tranfer state or TransferId * * @param state - transfer to get key from, or TransferId * @returns string containing a unique key for transfer */ function transferKey(state) { if ('_id' in state) return state._id; return `${state.direction}:${state.secrethash}`; } exports.transferKey = transferKey; const keyRe = new RegExp(`^(${Object.values(state_1.Direction).join('|')}):(0x[a-f0-9]{64})$`, 'i'); /** * Parse a transferKey into a TransferId object ({ secrethash, direction }) * * @param key - string to parse as transferKey * @returns secrethash, direction contained in transferKey */ function transferKeyToMeta(key) { const match = key.match(keyRe); (0, utils_5.assert)(match, 'Invalid transferKey format'); const [, direction, secrethash] = match; return { direction: direction, secrethash: secrethash }; } exports.transferKeyToMeta = transferKeyToMeta; const statusesMap = { [state_1.RaidenTransferStatus.expired]: (t) => t.expiredProcessed?.ts, [state_1.RaidenTransferStatus.unlocked]: (t) => t.unlockProcessed?.ts, [state_1.RaidenTransferStatus.expiring]: (t) => t.expired?.ts, [state_1.RaidenTransferStatus.unlocking]: (t) => t.unlock?.ts, [state_1.RaidenTransferStatus.registered]: (t) => t.secretRegistered?.ts, [state_1.RaidenTransferStatus.revealed]: (t) => t.secretReveal?.ts, [state_1.RaidenTransferStatus.requested]: (t) => t.secretRequest?.ts, [state_1.RaidenTransferStatus.closed]: (t) => t.channelClosed?.ts, [state_1.RaidenTransferStatus.received]: (t) => t.transferProcessed?.ts, [state_1.RaidenTransferStatus.pending]: (t) => t.transfer.ts, }; /** * @param state - Transfer state * @returns Transfer's status */ function transferStatus(state) { // order matters! from top to bottom priority, first match breaks loop for (const [s, g] of Object.entries(statusesMap)) { const ts = g(state); if (ts !== undefined) { return s; } } return state_1.RaidenTransferStatus.pending; } exports.transferStatus = transferStatus; /** * @param state - Transfer state * @returns Whether the transfer is considered completed */ function transferCompleted(state) { return !!(state.unlockProcessed || state.expiredProcessed || state.secretRegistered || state.channelSettled); } exports.transferCompleted = transferCompleted; /** * Convert a TransferState to a public RaidenTransfer object * * @param state - RaidenState.sent value * @returns Public raiden sent transfer info object */ function raidenTransfer(state) { const status = transferStatus(state); const startedAt = new Date(state.transfer.ts); const changedAt = new Date(statusesMap[status](state)); const transfer = state.transfer; const direction = state.direction; const value = transfer.lock.amount.sub(state.fee); const invalidSecretRequest = state.secretRequest && state.secretRequest.amount.lt(value); const success = state.secretReveal || state.unlock || state.secretRegistered ? true : invalidSecretRequest || state.expired || state.channelClosed ? false : undefined; const completed = transferCompleted(state); return { key: transferKey(state), secrethash: transfer.lock.secrethash, direction, status, initiator: transfer.initiator, partner: state.partner, target: transfer.target, metadata: transfer.metadata, paymentId: transfer.payment_identifier, chainId: transfer.chain_id.toNumber(), token: transfer.token, tokenNetwork: transfer.token_network_address, channelId: transfer.channel_identifier, value, fee: state.fee, amount: transfer.lock.amount, expiration: transfer.lock.expiration.toNumber(), startedAt, changedAt, success, completed, secret: state.secret, }; } exports.raidenTransfer = raidenTransfer; /** * Look for a BalanceProof matching given balanceHash among EnvelopeMessages in transfers * * @param db - Database instance * @param channel - Channel key of hash * @param direction - Direction of transfers to search * @param balanceHash - Expected balanceHash * @returns BalanceProof matching balanceHash or undefined */ function findBalanceProofMatchingBalanceHash$(db, channel, direction, balanceHash) { if (balanceHash === constants_1.HashZero) return (0, rxjs_1.of)(types_1.BalanceProofZero); return (0, rxjs_1.defer)(() => // use db.storage directly instead of db.transfers to search on historical data db.find({ selector: { channel: (0, utils_1.channelUniqueKey)(channel), direction } })).pipe((0, operators_1.mergeMap)(({ docs }) => (0, rxjs_1.from)(docs)), (0, operators_1.mergeMap)((doc) => { const transferState = (0, types_3.decode)(state_1.TransferState, doc); return (0, rxjs_1.from)([transferState.transfer, transferState.unlock, transferState.expired]); }), (0, operators_1.filter)(types_3.isntNil), (0, operators_1.map)(utils_2.getBalanceProofFromEnvelopeMessage), // will error observable if none matching is found (0, operators_1.first)((bp) => (0, utils_2.createBalanceHash)(bp) === balanceHash)); } exports.findBalanceProofMatchingBalanceHash$ = findBalanceProofMatchingBalanceHash$; /** * @param state - RaidenState or Observable of RaidenState to get transfer from * @param db - Try to fetch from db if not found on state * @param key - transferKey/_id to get * @returns Promise to TransferState */ async function getTransfer(state, db, key) { if (typeof key !== 'string') key = transferKey(key); if (!('address' in state)) state = await (0, rxjs_1.firstValueFrom)(state); const transfer = state.transfers[key]; if (transfer) return transfer; return (0, types_3.decode)(state_1.TransferState, await db.get(key)); } exports.getTransfer = getTransfer; // a very simple/small subset of Metadata, to be used only to ensure metadata.routes[*].route array // the t.exact wrapper ensure unlisted properties are removed after decoding const RawMetadataCodec = t.exact(t.type({ routes: t.array(t.exact(t.type({ route: t.array(t.string), address_metadata: t.unknown }))), })); /** * Prune metadata route without changing any of the original encoding * A clear metadata route have partner (address after ours) as first hop in routes[*].route array. * To be used only if partner requires !Capabilities.IMMUTABLE_METADATA * * @param address - Our address * @param metadata - Metadata object * @returns A copy of metadata with routes cleared (i.e. partner as first/next address) */ function clearMetadataRoute(address, metadata) { let decoded; try { decoded = (0, types_3.decode)(RawMetadataCodec, metadata); } catch (e) { return metadata; // if metadata isn't even RawMetadataCodec, just return it } const lowercaseAddr = address.toLowerCase(); return { ...decoded, routes: decoded.routes.map(({ route, ...rest }) => ({ ...rest, route: route.slice(route.findIndex((a) => a.toLowerCase() === lowercaseAddr) + 1), })), }; } exports.clearMetadataRoute = clearMetadataRoute; /** * Contructs transfer.request's payload paramaters from received PFS's Paths * * @param paths - Paths array coming from PFS * @param target - presence of target address * @param encryptSecret - Try to encrypt this secret object to target * @returns Respective members of transfer.request's payload */ function metadataFromPaths(paths, target, encryptSecret) { // paths may come with undesired parameters, so map&filter here before passing to metadata const routes = paths.map(({ path: route, fee: _, address_metadata }) => ({ route, ...(address_metadata && !(0, isEmpty_1.default)(address_metadata) ? { address_metadata } : {}), })); const viaPath = paths[0]; (0, utils_5.assert)(viaPath, 'empty paths'); const fee = viaPath.fee; const partner = viaPath.path[1]; // we're first address in route, partner is 2nd (0, utils_5.assert)(partner, 'empty route'); let partnerUserId; let partnerCaps; if (partner === target.meta.address) { partnerUserId = target.payload.userId; partnerCaps = target.payload.caps; } else { const partnerPresence = searchValidMetadata(viaPath.address_metadata, partner); partnerUserId = partnerPresence?.payload.userId; partnerCaps = partnerPresence?.payload.caps; } const via = { userId: partnerUserId }; let metadata = { routes }; // iff partner requires a clear route (to be first address), clear it; // in routes received from PFS, we're always first address and partner second if (!(0, utils_4.getCap)(partnerCaps, constants_2.Capabilities.IMMUTABLE_METADATA)) metadata = clearMetadataRoute(viaPath.path[0], metadata); else if (encryptSecret) { const encrypted = (0, bytes_1.hexlify)((0, eciesjs_1.encrypt)(target.payload.pubkey, Buffer.from((0, data_1.jsonStringify)(state_1.RevealedSecret.encode(encryptSecret))))); metadata = { ...metadata, secret: encrypted }; } return { resolved: true, metadata, fee, partner, ...via }; } exports.metadataFromPaths = metadataFromPaths; const EncryptedSecretMetadata = t.type({ secret: (0, types_3.HexString)() }); /** * @param metadata - Undecoded metadata * @param transfer - Transfer info * @param transfer."0" - Transfer's secrethash * @param transfer."1" - Transfer's effective received amount * @param transfer."2" - Transfer's paymendId * @param signer - Our effective signer (with `privateKey`) * @returns Secret, if decryption and all validations pass */ function decryptSecretFromMetadata(metadata, [secrethash, amount, paymentId], signer) { const privkey = signer.privateKey; if (!privkey) return; try { const encrypted = (0, types_3.decode)(EncryptedSecretMetadata, metadata).secret; const decrypted = (0, eciesjs_1.decrypt)(privkey, Buffer.from((0, bytes_1.arrayify)(encrypted))).toString(); const parsed = (0, types_3.decode)(state_1.RevealedSecret, (0, data_1.jsonParse)(decrypted)); (0, utils_5.assert)(amount.gte(parsed.amount) && getSecrethash(parsed.secret) === secrethash); (0, utils_5.assert)(!paymentId || !parsed.payment_identifier || paymentId.eq(parsed.payment_identifier)); return parsed.secret; } catch (e) { } } exports.decryptSecretFromMetadata = decryptSecretFromMetadata; /** * @param addressMetadata - metadata's address_metadata mapping * @param address - Address to search and validate * @returns AddressMetadata of given address */ function searchValidMetadata(addressMetadata, address) { // support address_metadata keys being both lowercase and checksummed addresses const metadata = addressMetadata?.[address] ?? addressMetadata?.[address.toLowerCase()]; if (metadata) { const presence = (0, utils_3.validateAddressMetadata)(metadata, address); if (presence) return presence; } } exports.searchValidMetadata = searchValidMetadata; /** * @param metadata - Transfer metadata to search on * @param address - Address metadata to search for * @returns Via object or undefined */ function searchValidViaAddress(metadata, address) { let userId; let decoded; try { decoded = (0, types_3.decode)(types_2.Metadata, metadata); } catch (e) { } if (!decoded || !address) return; for (const { address_metadata } of decoded.routes) { if ((userId = searchValidMetadata(address_metadata, address)?.payload.userId)) return { userId }; } } exports.searchValidViaAddress = searchValidViaAddress; //# sourceMappingURL=utils.js.map