UNPKG

@ckb-ccc/core

Version:

Core of CCC - CKBer's Codebase

260 lines (229 loc) 7.42 kB
import { Address } from "../../address/index.js"; import { Bytes, BytesLike, bytesConcat, bytesFrom } from "../../bytes/index.js"; import { Script, Transaction, TransactionLike, WitnessArgs, } from "../../ckb/index.js"; import { KnownScript } from "../../client/index.js"; import { Hasher, HasherKeecak256 } from "../../hasher/index.js"; import { Hex, HexLike, hexFrom } from "../../hex/index.js"; import { numToBytes } from "../../num/index.js"; import { reduceAsync } from "../../utils/index.js"; import { Signer, SignerSignType, SignerType } from "../signer/index.js"; /** * An abstract class extending Signer for Ethereum Virtual Machine (EVM) based signing operations. * This class provides methods to get EVM account, internal address, and signing transactions. * @public */ export abstract class SignerEvm extends Signer { get type(): SignerType { return SignerType.EVM; } get signType(): SignerSignType { return SignerSignType.EvmPersonal; } /** * Gets the EVM account associated with the signer. * * @returns A promise that resolves to a string representing the EVM account. */ abstract getEvmAccount(): Promise<Hex>; /** * Gets the internal address, which is the EVM account in this case. * * @returns A promise that resolves to a string representing the internal address. */ async getInternalAddress(): Promise<string> { return this.getEvmAccount(); } /** * Gets an array of Address objects representing the known script addresses for the signer. * * @returns A promise that resolves to an array of Address objects. */ async getAddressObjs(): Promise<Address[]> { const account = await this.getEvmAccount(); const addresses = await Promise.all([ this._getOmniLockAddresses(account), this._getPWLockAddresses(account), ]); return addresses.flat(); } _getOmniLockAddresses(account: HexLike): Promise<Address[]> { return Promise.all([ this._getOmniLockEvmAddressObj(account), this._getOmniLockOldEvmAddressObj(account), ]); } async _getPWLockAddresses(account: HexLike): Promise<Address[]> { const addr = await this._getPWLockEvmAddressObj(account); if (!addr) { return []; } return [addr]; } async _getOmniLockEvmAddressObj(account: HexLike): Promise<Address> { return Address.fromKnownScript( this.client, KnownScript.OmniLock, hexFrom([0x12, ...bytesFrom(account), 0x00]), ); } async _getOmniLockOldEvmAddressObj(account: HexLike): Promise<Address> { return Address.fromKnownScript( this.client, KnownScript.OmniLock, hexFrom([0x1, ...bytesFrom(account), 0x00]), ); } async _getPWLockEvmAddressObj( account: HexLike, ): Promise<Address | undefined> { try { return Address.fromKnownScript( this.client, KnownScript.PWLock, hexFrom(bytesFrom(account)), ); } catch {} return; } /** * prepare a transaction before signing. This method is not implemented and should be overridden by subclasses. * * @param txLike - The transaction to prepare, represented as a TransactionLike object. * @returns A promise that resolves to the prepared Transaction object. */ async prepareTransaction(txLike: TransactionLike): Promise<Transaction> { const tx = Transaction.from(txLike); if ( (await tx.findInputIndexByLockId( await this.client.getKnownScript(KnownScript.OmniLock), this.client, )) !== undefined ) { await tx.addCellDepsOfKnownScripts(this.client, KnownScript.OmniLock); } if ( (await tx.findInputIndexByLockId( await this.client.getKnownScript(KnownScript.PWLock), this.client, )) !== undefined ) { await tx.addCellDepsOfKnownScripts(this.client, KnownScript.PWLock); } const account = await this.getEvmAccount(); const omniLockAddresses = await this._getOmniLockAddresses(account); const pwLockAddresses = await this._getPWLockAddresses(account); const omniTx = reduceAsync( omniLockAddresses, (tx: Transaction, { script }) => tx.prepareSighashAllWitness(script, 85, this.client), tx, ); return reduceAsync( pwLockAddresses, (tx: Transaction, { script }) => tx.prepareSighashAllWitness(script, 65, this.client), omniTx, ); } /** * Signs a transaction without modifying it. * * @param txLike - The transaction to sign, represented as a TransactionLike object. * @returns A promise that resolves to a signed Transaction object. */ async signOnlyTransaction(txLike: TransactionLike): Promise<Transaction> { let tx = Transaction.from(txLike); const account = await this.getEvmAccount(); const { script: evmScript } = await this._getOmniLockEvmAddressObj(account); const { script: oldEvmScript } = await this._getOmniLockOldEvmAddressObj(account); tx = await this._signOmniLockScriptForTransaction( tx, evmScript, (hash) => `CKB transaction: ${hash}`, ); tx = await this._signOmniLockScriptForTransaction( tx, oldEvmScript, (hash) => bytesFrom(hash), ); const pwAddress = await this._getPWLockEvmAddressObj(account); if (pwAddress) { tx = await this._signPWLockScriptForTransaction( tx, pwAddress.script, (hash) => bytesFrom(hash), ); } return tx; } async _signOmniLockScriptForTransaction( tx: Transaction, script: Script, messageTransformer: (hash: string) => BytesLike, ): Promise<Transaction> { const info = await this._signPersonalEvmForTransaction( tx, script, messageTransformer, ); if (!info) { return tx; } const witness = WitnessArgs.fromBytes(tx.witnesses[info.position]); witness.lock = hexFrom( bytesConcat( numToBytes(5 * 4 + info.signature.length, 4), numToBytes(4 * 4, 4), numToBytes(5 * 4 + info.signature.length, 4), numToBytes(5 * 4 + info.signature.length, 4), numToBytes(info.signature.length, 4), info.signature, ), ); tx.setWitnessArgsAt(info.position, witness); return tx; } async _signPWLockScriptForTransaction( tx: Transaction, script: Script, messageTransformer: (hash: string) => BytesLike, ): Promise<Transaction> { const info = await this._signPersonalEvmForTransaction( tx, script, messageTransformer, new HasherKeecak256(), ); if (!info) { return tx; } const witness = WitnessArgs.fromBytes(tx.witnesses[info.position]); witness.lock = hexFrom(info.signature); tx.setWitnessArgsAt(info.position, witness); return tx; } async _signPersonalEvmForTransaction( tx: Transaction, script: Script, messageTransformer: (hash: string) => BytesLike, hasher?: Hasher, ): Promise<{ signature: Bytes; position: number } | undefined> { const info = await tx.getSignHashInfo(script, this.client, hasher); if (!info) { return; } const signature = bytesFrom( await this.signMessageRaw(messageTransformer(info.message)), ); if (signature[signature.length - 1] >= 27) { signature[signature.length - 1] -= 27; } return { signature, position: info.position }; } }