UNPKG

@citizenwallet/sdk

Version:

An sdk to easily work with citizen wallet.

281 lines 16 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.revokeSession = exports.getTwoFAAddress = exports.isSessionExpired = exports.confirmSession = exports.verifyIncomingSessionRequest = exports.requestSession = exports.verifySessionConfirm = exports.verifySessionRequest = exports.generateSessionHash = exports.generateSessionRequestHash = exports.generateSessionSalt = void 0; const ethers_1 = require("ethers"); const bundler_1 = require("../bundler"); const SessionManagerModule_json_1 = __importDefault(require("../abi/SessionManagerModule.json")); const TwoFAFactory_json_1 = __importDefault(require("../abi/TwoFAFactory.json")); const sessionManagerInterface = new ethers_1.Interface(SessionManagerModule_json_1.default.abi); const twoFAFactoryInterface = new ethers_1.Interface(TwoFAFactory_json_1.default.abi); /** * Generates a unique salt for a session based on a source and type. * The salt is created by hashing the concatenated source and type with a colon separator. * * @param {string} params.source - The source identifier for the session * @param {string} params.type - The type identifier for the session * @returns {string} A bytes32 hash of the source and type combination */ const generateSessionSalt = ({ source, type, }) => { return (0, ethers_1.id)(`${source}:${type}`); }; exports.generateSessionSalt = generateSessionSalt; /** * Generates a hash for a session request by encoding and hashing the session parameters. * Uses ABI encoding to ensure compatibility with the Dart implementation. * * @param {CommunityConfig} params.community - Instance of CommunityConfig * @param {string} params.sessionOwner - The address of the session owner (address of private key) * @param {string} params.salt - The unique salt generated for this session * @param {number} params.expiry - UTC timestamp in seconds when the session expires * @returns {string} A bytes32 hash of the encoded session request parameters */ const generateSessionRequestHash = ({ community, sessionOwner, salt, expiry, }) => { const sessionProvider = community.primarySessionConfig.provider_address; // Use ABI encoding to match the Dart implementation const abiCoder = new ethers_1.AbiCoder(); const packedData = abiCoder.encode(['address', 'address', 'bytes32', 'uint48'], [sessionProvider, sessionOwner, salt, BigInt(expiry)]); const result = (0, ethers_1.keccak256)(packedData); return result; }; exports.generateSessionRequestHash = generateSessionRequestHash; /** * Generates the final session hash by combining the session request hash with a challenge number. * Uses ABI encoding to ensure compatibility with the Dart implementation. * * @param {string} params.sessionRequestHash - The hash generated from generateSessionRequestHash * @param {number} params.challenge - The challenge number to combine with the session request hash * @returns {string} A bytes32 hash of the encoded session parameters and challenge */ const generateSessionHash = ({ sessionRequestHash, challenge, }) => { // Use ABI encoding to match the Dart implementation const abiCoder = new ethers_1.AbiCoder(); const packedData = abiCoder.encode(['bytes32', 'uint256'], [sessionRequestHash, BigInt(challenge)]); return (0, ethers_1.keccak256)(packedData); }; exports.generateSessionHash = generateSessionHash; /** * Verifies a session request by validating the signature against the session owner's address. * This function combines the session salt generation and request hash generation to verify * that the signature was created by the session owner. * * @param {CommunityConfig} params.community - Instance of CommunityConfig * @param {string} params.sessionOwner - The address of the session owner to verify against * @param {string} params.source - The source identifier used in generating the session salt * @param {string} params.type - The type identifier used in generating the session salt * @param {number} params.expiry - UTC timestamp in seconds when the session expires * @param {string} params.signature - The signature to verify, created by the session owner * @returns {boolean} True if the signature is valid and matches the session owner's address */ const verifySessionRequest = ({ community, sessionOwner, source, type, expiry, signature, }) => { const salt = (0, exports.generateSessionSalt)({ source, type }); const sessionRequestHash = (0, exports.generateSessionRequestHash)({ community, sessionOwner, salt, expiry, }); const recoveredAddress = (0, ethers_1.verifyMessage)((0, ethers_1.getBytes)(sessionRequestHash), signature); return recoveredAddress === sessionOwner; }; exports.verifySessionRequest = verifySessionRequest; /** * Verifies a session confirmation by validating the signed session hash against the session owner's address. * This function checks if the signature of the session hash was created by the session owner. * * @param {string} params.sessionOwner - The address of the session owner to verify against * @param {string} params.sessionHash - The session hash generated from generateSessionHash * @param {string} params.signedSessionHash - The signature of the session hash to verify * @returns {boolean} True if the signature is valid and matches the session owner's address */ const verifySessionConfirm = ({ sessionOwner, sessionHash, signedSessionHash, }) => { const recoveredAddress = (0, ethers_1.verifyMessage)((0, ethers_1.getBytes)(sessionHash), signedSessionHash); return recoveredAddress === sessionOwner; }; exports.verifySessionConfirm = verifySessionConfirm; /** * Submits a session request to the session manager contract through a bundler service. * This function encodes the session parameters and sends a transaction to create a new session. * The challenge expiry is automatically set to 120 seconds from the current time. * * @param {CommunityConfig} params.community - An instance of CommunityConfig * @param {Wallet} params.signer - The wallet instance used to sign the transaction * @param {string} params.sessionSalt - The unique salt generated for this session * @param {string} params.sessionRequestHash - The hash of the session request parameters * @param {string} params.signedSessionRequestHash - The signature of the session request hash * @param {string} params.signedSessionHash - The signature of the session hash * @param {number} params.sessionExpiry - UTC timestamp in seconds when the session expires * @returns {Promise<string>} The transaction hash of the session request */ const requestSession = async ({ community, signer, sessionSalt, sessionRequestHash, signedSessionRequestHash, signedSessionHash, sessionExpiry, }) => { const sessionModuleAddress = community.primarySessionConfig.module_address; const sessionProvider = community.primarySessionConfig.provider_address; const bundler = new bundler_1.BundlerService(community); const challengeExpiry = Math.floor(Date.now() / 1000) + 120; const data = (0, ethers_1.getBytes)(sessionManagerInterface.encodeFunctionData('request', [ sessionSalt, sessionRequestHash, signedSessionRequestHash, signedSessionHash, sessionExpiry, challengeExpiry, ])); const tx = await bundler.call(signer, sessionModuleAddress, sessionProvider, data); return tx; }; exports.requestSession = requestSession; /** * Verifies an incoming session request by checking its validity against the session manager contract. * Performs multiple validations: * 1. Checks if the session request exists in the contract * 2. Validates that the session has not expired * 3. Validates that the challenge period has not expired * 4. Verifies the signature matches the stored signature * * @param {CommunityConfig} params.community - Instance of CommunityConfig * @param {Wallet} params.signer - The wallet instance used to sign and verify the session * @param {string} params.sessionRequestHash - The hash of the session request to verify * @param {string} params.sessionHash - The session hash to sign and compare * @returns {Promise<boolean>} True if the session request is valid and signatures match, false otherwise * @throws {Error} If session request is not found, expired, or challenge period has ended */ const verifyIncomingSessionRequest = async ({ community, signer, sessionRequestHash, sessionHash, options, }) => { const { accountFactoryAddress } = options ?? {}; try { // Get the session manager contract address const sessionModuleAddress = community.primarySessionConfig.module_address; const sessionProvider = community.primarySessionConfig.provider_address; const rpcProvider = new ethers_1.JsonRpcProvider(community.getRPCUrl(accountFactoryAddress)); const contract = new ethers_1.Contract(sessionModuleAddress, sessionManagerInterface, rpcProvider); const result = await contract.sessionRequests(sessionProvider, sessionRequestHash); if (result.length < 5) { throw new Error('Session request not found'); } // check the expiry const expiry = Number(result[0]); const now = Math.floor(Date.now() / 1000); if (expiry < now) { throw new Error('Session request expired'); } // check the challenge expiry const challengeExpiry = Number(result[1]); if (challengeExpiry < now) { throw new Error('Challenge expired'); } // Extract the stored signedSessionHash from the result const storedSignedSessionHash = result[2]; // Sign the provided sessionHash with the signer const calculatedSignedSessionHash = await signer.signMessage((0, ethers_1.getBytes)(sessionHash)); // Compare the stored signedSessionHash with the provided one return storedSignedSessionHash === calculatedSignedSessionHash; } catch (error) { console.error('Error verifying incoming session request:', error); return false; } }; exports.verifyIncomingSessionRequest = verifyIncomingSessionRequest; /** * Confirms a session request by submitting the confirmation transaction through a bundler service. * This function encodes the session confirmation parameters and sends them to the session manager contract. * * @param {CommunityConfig} params.community - Instance of CommunityConfig containing contract addresses * @param {Wallet} params.signer - The wallet instance used to sign the transaction * @param {string} params.sessionRequestHash - The hash of the original session request * @param {string} params.sessionHash - The final session hash to confirm * @param {string} params.signedSessionHash - The signature of the session hash * @returns {Promise<string>} The transaction hash of the confirmation transaction */ const confirmSession = async ({ community, signer, sessionRequestHash, sessionHash, signedSessionHash, }) => { const sessionModuleAddress = community.primarySessionConfig.module_address; const sessionProvider = community.primarySessionConfig.provider_address; const bundler = new bundler_1.BundlerService(community); const data = (0, ethers_1.getBytes)(sessionManagerInterface.encodeFunctionData('confirm', [ sessionRequestHash, sessionHash, signedSessionHash, ])); const tx = await bundler.call(signer, sessionModuleAddress, sessionProvider, data); return tx; }; exports.confirmSession = confirmSession; /** * Checks if a session between an account and owner has expired by querying the session manager contract. * This function makes a direct call to the contract's isExpired method. * * @param {CommunityConfig} params.community - Instance of CommunityConfig containing contract addresses and RPC URL * @param {string} params.account - The account address to check the session for * @param {string} params.owner - The owner address to check the session against * @returns {Promise<boolean>} True if the session has expired or doesn't exist, false if the session is still valid * @throws {Error} Logs error and returns false if the contract call fails */ const isSessionExpired = async ({ community, account, owner, options, }) => { const { accountFactoryAddress } = options ?? {}; // Get the session manager contract address const sessionModuleAddress = community.primarySessionConfig.module_address; const rpcProvider = new ethers_1.JsonRpcProvider(community.getRPCUrl(accountFactoryAddress)); const contract = new ethers_1.Contract(sessionModuleAddress, sessionManagerInterface, rpcProvider); try { const result = await contract.isExpired(account, owner); return result; } catch (error) { console.error('Error checking if session is expired:', error); return true; } }; exports.isSessionExpired = isSessionExpired; /** * Retrieves the deterministic address for a 2FA account based on the community configuration, source, and type. * This function queries the 2FA factory contract to compute the address that would be created with these parameters. * * @param {CommunityConfig} params.community - Instance of CommunityConfig containing factory and provider addresses * @param {string} params.source - The source identifier used to generate the salt * @param {string} params.type - The type identifier used to generate the salt * @returns {Promise<string | null>} The computed 2FA address if successful, null if the computation fails */ const getTwoFAAddress = async ({ community, source, type, options, }) => { const { accountFactoryAddress, sessionFactoryAddress, sessionProviderAddress, rpcUrl, } = options ?? {}; const factoryAddress = sessionFactoryAddress ?? community.primarySessionConfig.factory_address; const providerAddress = sessionProviderAddress ?? community.primarySessionConfig.provider_address; const salt = (0, exports.generateSessionSalt)({ source, type }); const saltBigInt = BigInt(salt); const resolvedRpcUrl = rpcUrl ?? community.getRPCUrl(accountFactoryAddress); const rpcProvider = new ethers_1.JsonRpcProvider(resolvedRpcUrl); const contract = new ethers_1.Contract(factoryAddress, twoFAFactoryInterface, rpcProvider); try { const result = await contract.getFunction('getAddress')(providerAddress, saltBigInt); return result; } catch (error) { console.error('Error getting twoFA address:', error); return null; } }; exports.getTwoFAAddress = getTwoFAAddress; /** * Revokes an active session between a signer and an account through the session manager contract. * This function sends a revocation transaction using the bundler service. * * @param {CommunityConfig} params.community - Instance of CommunityConfig containing contract addresses * @param {Wallet} params.signer - The wallet instance used to sign and revoke the session * @param {string} params.account - The account address from which to revoke the session * @returns {Promise<string|null>} The transaction hash if successful, null if the revocation fails */ const revokeSession = async ({ community, signer, account, }) => { const sessionModuleAddress = community.primarySessionConfig.module_address; const bundler = new bundler_1.BundlerService(community); const data = (0, ethers_1.getBytes)(sessionManagerInterface.encodeFunctionData('revoke', [signer.address])); try { const tx = await bundler.call(signer, sessionModuleAddress, account, data); return tx; } catch (error) { console.error('Error revoking session:', error); return null; } }; exports.revokeSession = revokeSession; //# sourceMappingURL=index.js.map