From b54833a9519cf669407958d9bf56e7bd5b867e76 Mon Sep 17 00:00:00 2001 From: mtyszczak <mateusz.tyszczak@gmail.com> Date: Mon, 24 Mar 2025 15:58:29 +0100 Subject: [PATCH] Implement hb-auth signer extension --- .../__tests__/assets/fixture.ts | 2 +- .../__tests__/index.spec.ts | 4 ++ examples/ts/signature-extension/package.json | 2 + .../test/hb-auth-worker.js | 1 + .../ts/signature-extension/test/index.html | 56 ++++++++++----- ts/packages/signers-hb-auth/README.md | 37 ++++++++++ ts/packages/signers-hb-auth/package.json | 40 +++++++++++ ts/packages/signers-hb-auth/src/index.ts | 68 +++++++++++++++++++ ts/packages/signers-hb-auth/tsconfig.json | 14 ++++ ts/packages/signers-keychain/src/index.ts | 3 + ts/pnpm-lock.yaml | 42 ++++++++++++ 11 files changed, 252 insertions(+), 17 deletions(-) create mode 120000 examples/ts/signature-extension/test/hb-auth-worker.js create mode 100644 ts/packages/signers-hb-auth/README.md create mode 100644 ts/packages/signers-hb-auth/package.json create mode 100644 ts/packages/signers-hb-auth/src/index.ts create mode 100644 ts/packages/signers-hb-auth/tsconfig.json diff --git a/examples/ts/signature-extension/__tests__/assets/fixture.ts b/examples/ts/signature-extension/__tests__/assets/fixture.ts index 30e4aa430..6e09a2dd3 100644 --- a/examples/ts/signature-extension/__tests__/assets/fixture.ts +++ b/examples/ts/signature-extension/__tests__/assets/fixture.ts @@ -12,7 +12,7 @@ export const test = base.extend<{ }>({ context: async ({}, use) => { console.log('Launched browser'); - const browserContext = await chromium.launchPersistentContext(''); + const browserContext = await chromium.launchPersistentContext('', { headless: typeof process.env.PLAYWRIGHT_HEADLESS === "undefined" }); console.log('Before use browserContext'); await use(browserContext); diff --git a/examples/ts/signature-extension/__tests__/index.spec.ts b/examples/ts/signature-extension/__tests__/index.spec.ts index 24c41a95d..273f89417 100644 --- a/examples/ts/signature-extension/__tests__/index.spec.ts +++ b/examples/ts/signature-extension/__tests__/index.spec.ts @@ -3,6 +3,10 @@ import { expect, Page } from "@playwright/test"; test.describe('Signature extension tests', () => { test('Should be able to sign transction using key chain extension.', async ({ page, extensionId, context, baseDirectoryPath, testedAccountAuthorityData}) => { + page.on("console", (msg) => { + console.log(`[${msg.type()}]>> Page console: ${msg.text()}`); + }); + page.setViewportSize({ width: 500, height: 700 }); //////////////// Import settings begin (containing mirrornet endpoint configuration) diff --git a/examples/ts/signature-extension/package.json b/examples/ts/signature-extension/package.json index 75df6e5cd..536f45f47 100644 --- a/examples/ts/signature-extension/package.json +++ b/examples/ts/signature-extension/package.json @@ -9,8 +9,10 @@ }, "dependencies": { "@hiveio/beekeeper": "1.27.10-stable.250305202831", + "@hiveio/hb-auth": "0.0.1-stable.250131124251", "@hiveio/wax": "file:../../../ts", "@hiveio/wax-signers-beekeeper": "file:../../../ts/packages/signers-beekeeper", + "@hiveio/wax-signers-hb-auth": "file:../../../ts/packages/signers-hb-auth", "@hiveio/wax-signers-keychain": "file:../../../ts/packages/signers-keychain", "@hiveio/wax-signers-metamask": "file:../../../ts/packages/signers-metamask", "@hiveio/wax-signers-peakvault": "file:../../../ts/packages/signers-peakvault" diff --git a/examples/ts/signature-extension/test/hb-auth-worker.js b/examples/ts/signature-extension/test/hb-auth-worker.js new file mode 120000 index 000000000..2f06d0b6b --- /dev/null +++ b/examples/ts/signature-extension/test/hb-auth-worker.js @@ -0,0 +1 @@ +../node_modules/@hiveio/hb-auth/dist/worker.js \ No newline at end of file diff --git a/examples/ts/signature-extension/test/index.html b/examples/ts/signature-extension/test/index.html index fd4fef120..4d46dd3cf 100644 --- a/examples/ts/signature-extension/test/index.html +++ b/examples/ts/signature-extension/test/index.html @@ -14,6 +14,7 @@ <button onclick="usePeakVault()">Use Peak Vault</button> <button onclick="useMetaMask()">Use MetaMask</button> <button onclick="useBeekeeper()">Use Beekeeper</button> + <button onclick="useHbAuth()">Use HBAuth</button> <pre><code id="operating-private-key"></code></pre> <pre><code id="tx-result"></code></pre> @@ -26,6 +27,8 @@ import PeakVaultProvider from "@hiveio/wax-signers-peakvault"; import MetaMaskProvider from "@hiveio/wax-signers-metamask"; import BeekeeperProvider from "@hiveio/wax-signers-beekeeper"; + import HBAuthProvider from "@hiveio/wax-signers-hb-auth"; + import { OfflineClient } from "@hiveio/hb-auth"; const txResult = document.getElementById('tx-result'); const operatingPrivateKeyPlaceholder = document.getElementById('operating-private-key'); @@ -34,21 +37,6 @@ (async()=> { const testEnv = await prepareTestingEnvironemnt(); const accountName = testEnv.accountName; - const keychainProvider = KeychainProvider.for(accountName, testEnv.role); - let beekeeperProvider; - try { - beekeeperProvider = await BeekeeperProvider.for(testEnv.preparedBeekeeperWallet, testEnv.publicKey); - } catch (error) { - console.error('Beekeeper provider not available', error); - } - const peakVaultProvider = PeakVaultProvider.for(accountName, testEnv.role); - let metaMaskProvider; - try { - metaMaskProvider = await MetaMaskProvider.for(0); - await metaMaskProvider.installSnap(); - } catch (error) { - console.error('Metamask provider not available', error); - } operatingPrivateKeyPlaceholder.textContent = `${accountName}@${testEnv.role} private key to be imported to wallets: ${testEnv.privateKey}`; @@ -97,15 +85,51 @@ await tx.sign(provider); await verifySignature(tx); } catch (error) { - console.error(error); + console.error('Error signing transaction:', error); txResult.textContent = `Error: ${error.message}`; } }; + const keychainProvider = KeychainProvider.for(accountName, testEnv.role); + window.useKeychain = () => void sign(keychainProvider); + + let beekeeperProvider; + try { + beekeeperProvider = await BeekeeperProvider.for(testEnv.preparedBeekeeperWallet, testEnv.publicKey); + } catch (error) { + console.error('Beekeeper provider not available', error); + } + const hbAuthClient = new OfflineClient({ + chainId: testEnv.configuredChain.chainId, + node: testEnv.configuredChain.endpointUrl, + workerUrl: new URL('./hb-auth-worker.js', import.meta.url), + sessionTimeout: 900 + }); + const hbAuthProvider = await HBAuthProvider.for(hbAuthClient); + try { + await hbAuthClient.initialize(); + const registeredUser = await hbAuthClient.getRegisteredUserByUsername(accountName); + if (registeredUser) + await hbAuthClient.authenticate(accountName, "password", testEnv.role); + else + await hbAuthClient.register(accountName, "password", testEnv.privateKey, testEnv.role); + } catch (error) { + console.error('Could not initialize hb-auth', error); + } + const peakVaultProvider = PeakVaultProvider.for(accountName, testEnv.role); + let metaMaskProvider; + try { + metaMaskProvider = await MetaMaskProvider.for(0); + await metaMaskProvider.installSnap(); + } catch (error) { + console.error('Metamask provider not available', error); + } + window.usePeakVault = () => void sign(peakVaultProvider); window.useMetaMask = () => void sign(metaMaskProvider); window.useBeekeeper = () => void sign(beekeeperProvider); + window.useHbAuth = () => void sign(hbAuthProvider); })(); </script> </body> diff --git a/ts/packages/signers-hb-auth/README.md b/ts/packages/signers-hb-auth/README.md new file mode 100644 index 000000000..6abd32c5e --- /dev/null +++ b/ts/packages/signers-hb-auth/README.md @@ -0,0 +1,37 @@ +# @hiveio/wax-signers-hbauth + +Wax signer library extending transaction signing possibilities by a 3rd party Web-only extension - hb-auth + +## Example usage + +```ts +import { createHiveChain } from "@hiveio/wax"; +import HBAuthProvider from "@hiveio/wax-signers-hb-auth"; + +const chain = await createHiveChain(); + +const provider = HBAuthProvider.for(hbAuthClient); + +// Create a transaction using the Wax Hive chain instance +const tx = await chain.createTransaction(); + +// Perform some operations, e.g. push the vote operation: +tx.pushOperation({ + vote: { + voter: "alice", + author: "bob", + permlink: "example-post", + weight: 10000 + } +}); + +// Wait for the keychain to sign the transaction +await tx.sign(provider); + +// broadcast the transaction +await chain.broadcast(tx); +``` + +## License + +See license in [LICENSE.md](LICENSE.md) file diff --git a/ts/packages/signers-hb-auth/package.json b/ts/packages/signers-hb-auth/package.json new file mode 100644 index 000000000..c990fda27 --- /dev/null +++ b/ts/packages/signers-hb-auth/package.json @@ -0,0 +1,40 @@ +{ + "name": "@hiveio/wax-signers-hb-auth", + "version": "0.0.0-Run-Prepack", + "description": "Wax signer library extending transaction signing possibilities by a 3rd party Web-only extension - hb-auth", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "module", + "private": false, + "scripts": { + "build": "tsc", + "prepack": "jq --argfile source ../../package.json '.version = $source.version | .publishConfig.registry = $source.publishConfig.registry | .publishConfig.tag = $source.publishConfig.tag' package.json > package.json.tmp && mv package.json.tmp package.json" + }, + "devDependencies": { + "typescript": "catalog:typescript-toolset" + }, + "dependencies": { + "@hiveio/wax": "workspace:../..", + "@hiveio/hb-auth": "0.0.1-stable.250131124251" + }, + "files": [ + "dist/index.d.ts", + "dist/index.js", + "README.md", + "LICENSE.md" + ], + "license": "SEE LICENSE IN LICENSE.md", + "keywords": [ + "wax", + "blockchain", + "hive" + ], + "repository": { + "type": "git", + "url": "https://gitlab.syncad.com/hive/wax.git" + }, + "publishConfig": { + "registry": "https://RegistryPlaceholder", + "tag": "DistTagPlaceholder" + } +} \ No newline at end of file diff --git a/ts/packages/signers-hb-auth/src/index.ts b/ts/packages/signers-hb-auth/src/index.ts new file mode 100644 index 000000000..c49ca0290 --- /dev/null +++ b/ts/packages/signers-hb-auth/src/index.ts @@ -0,0 +1,68 @@ +import type { IOnlineSignatureProvider, ITransaction } from "@hiveio/wax"; + +import { type OfflineClient, type OnlineClient } from "@hiveio/hb-auth"; + +// We do not extend from WaxError to avoid runtime dependencies, such as: /vite or /web - without it we can import only types +export class WaxHBAuthProviderError extends Error {} + +/** + * Wax transaction signature provider using the hb-auth. + * + * @example + * ``` + * const provider = HBAuthProvider.for(hbAuthClient); + * + * // Create a transaction using the Wax Hive chain instance + * const tx = await chain.createTransaction(); + * + * // Perform some operations, e.g. pushing operations... + * + * // Sign the transaction + * await tx.sign(provider); + * + * // broadcast + * await chain.broadcast(tx); + * ``` + */ +class HBAuthProvider implements IOnlineSignatureProvider { + private constructor( + public readonly client: OnlineClient | OfflineClient, + public readonly username?: string + ) {} + + public static for(client: OnlineClient | OfflineClient, username?: string): HBAuthProvider { + return new HBAuthProvider(client, username); + } + + public async signTransaction(transaction: ITransaction): Promise<void> { + const requiredAuthorities = transaction.requiredAuthorities; + + const signatures: string[] = []; + + const digest = transaction.sigDigest; + + for(const auth in requiredAuthorities) + if (auth !== "other") + for(const actor of requiredAuthorities[auth]) + if (this.username === undefined || actor === this.username) + signatures.push(await this.client.sign(actor, digest, auth as 'posting' | 'active' | 'owner')); + + if (signatures.length === 0) + throw new WaxHBAuthProviderError(`Failed to sign the transaction`); + + for(const signature of signatures) + transaction.sign(signature); + } +} + +export interface WaxHBAuthProviderCreator { + /** + * We assume you already called #initialize() on the client and client has imported the keys. + * + * @param client - The hb-auth client instance. + * @param username - The username to sign the transaction with. If not provided - every user imported to the hb-auth will be allowed to sign the transaction. + */ + for(client: OnlineClient | OfflineClient, username?: string): HBAuthProvider; +} + +export default HBAuthProvider as WaxHBAuthProviderCreator; diff --git a/ts/packages/signers-hb-auth/tsconfig.json b/ts/packages/signers-hb-auth/tsconfig.json new file mode 100644 index 000000000..25ef87095 --- /dev/null +++ b/ts/packages/signers-hb-auth/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../npm-common-config/ts-common/tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "baseUrl": ".", + "outDir": "./dist", + "incremental": true, + "tsBuildInfoFile": "./dist/.tsbuildinfo", + "skipLibCheck": true + }, + "include": [ + "./src" + ] +} \ No newline at end of file diff --git a/ts/packages/signers-keychain/src/index.ts b/ts/packages/signers-keychain/src/index.ts index b997ac21b..1337a7f12 100644 --- a/ts/packages/signers-keychain/src/index.ts +++ b/ts/packages/signers-keychain/src/index.ts @@ -54,6 +54,9 @@ class KeychainProvider implements IOnlineSignatureProvider { } public async signTransaction(transaction: ITransaction): Promise<void> { + if (!(await KeychainProvider.keychain.isKeychainInstalled())) + throw new WaxKeychainProviderError(`Keychain is not installed`); + const data = await KeychainProvider.keychain.signTx({ method: this.role, username: this.accountName, diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 8eae27819..15f09f82c 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -201,6 +201,19 @@ importers: specifier: catalog:typescript-toolset version: 5.7.3 + packages/signers-hb-auth: + dependencies: + '@hiveio/hb-auth': + specifier: 0.0.1-stable.250131124251 + version: 0.0.1-stable.250131124251 + '@hiveio/wax': + specifier: workspace:../.. + version: link:../.. + devDependencies: + typescript: + specifier: catalog:typescript-toolset + version: 5.7.3 + packages/signers-keychain: dependencies: '@hiveio/wax': @@ -428,9 +441,20 @@ packages: resolution: {integrity: sha1-mPx0QrDh3NSPSOYGyR1SUyi3lyA=, tarball: https://gitlab.syncad.com/api/v4/projects/198/packages/npm/@hiveio/beekeeper/-/@hiveio/beekeeper-1.27.10-stable.250305202831.tgz} engines: {node: ^20.11 || >= 21.2} + '@hiveio/beekeeper@1.27.8-stable.250131103618': + resolution: {integrity: sha1-0IYuFBbxIIKfRTrI6ZZc7Jf+qLA=, tarball: https://gitlab.syncad.com/api/v4/projects/198/packages/npm/@hiveio/beekeeper/-/@hiveio/beekeeper-1.27.8-stable.250131103618.tgz} + engines: {node: ^20.11 || >= 21.2} + '@hiveio/dhive@1.3.2': resolution: {integrity: sha512-kJjp3TbpIlODxjJX4BWwvOf+cMxT8CFH/mNQ40RRjR2LP0a4baSWae1G+U/q/NtgjsIQz6Ja40tvnw6KF12I+g==, tarball: https://registry.npmjs.org/@hiveio/dhive/-/dhive-1.3.2.tgz} + '@hiveio/hb-auth@0.0.1-stable.250131124251': + resolution: {integrity: sha1-HX9GbxRjpo+ezDR5SAvuEIVr6II=, tarball: https://gitlab.syncad.com/api/v4/projects/429/packages/npm/@hiveio/hb-auth/-/@hiveio/hb-auth-0.0.1-stable.250131124251.tgz} + + '@hiveio/wax@1.27.6-rc7-stable.250131113706': + resolution: {integrity: sha1-Xi2VV8y9T6MdBHOBSzxZNpmGnF8=, tarball: https://gitlab.syncad.com/api/v4/projects/419/packages/npm/@hiveio/wax/-/@hiveio/wax-1.27.6-rc7-stable.250131113706.tgz} + engines: {node: ^20.11 || >= 21.2} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -978,6 +1002,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comlink@4.4.2: + resolution: {integrity: sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2589,6 +2616,8 @@ snapshots: '@hiveio/beekeeper@1.27.10-stable.250305202831': {} + '@hiveio/beekeeper@1.27.8-stable.250131103618': {} + '@hiveio/dhive@1.3.2': dependencies: '@ecency/bytebuffer': 6.0.0 @@ -2607,6 +2636,17 @@ snapshots: transitivePeerDependencies: - encoding + '@hiveio/hb-auth@0.0.1-stable.250131124251': + dependencies: + '@hiveio/wax': 1.27.6-rc7-stable.250131113706 + comlink: 4.4.2 + + '@hiveio/wax@1.27.6-rc7-stable.250131113706': + dependencies: + '@hiveio/beekeeper': 1.27.8-stable.250131103618 + events: 3.3.0 + long: 5.2.3 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3189,6 +3229,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comlink@4.4.2: {} + commander@2.20.3: {} commondir@1.0.1: {} -- GitLab