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