diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c5b79c..8cfb9ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.9.1 + +### Changed + +- **Test host now matches signing accounts by SS58 address only.** `getPairByAddress` previously had a fallback that matched a raw hex public key (`0x…`) against keypairs, which silently masked a real bug: `@novasamatech/host-api-wrapper`'s `getLegacyAccountSigner` was sending the `signer` field as `toHex(publicKey)` instead of an SS58 address. Real wallets match by SS58 only, so the fallback made the test host more lenient than production. Removing it makes the legacy sign-raw/sign-payload paths fail here exactly as they do against a real wallet, guarding against regressions of [paritytech/product-sdk#156](https://github.com/paritytech/product-sdk/issues/156). +- **`test/test-product.ts` `trySignRaw` now exercises the real signer path** (`getLegacyAccountSigner(account).signBytes(...)`) instead of hand-building the wire request, so the integration test covers the actual SDK code path. Requires the fixed wrapper (`@novasamatech/host-api-wrapper` ≥ 0.8.1) to pass. + ## 0.9.0 ### Changed diff --git a/forum-post.md b/forum-post.md index af8ed4d..02e740a 100644 --- a/forum-post.md +++ b/forum-post.md @@ -1,3 +1,9 @@ +# host-api-test-sdk 0.9.1 + +## Stricter (real-wallet-like) account matching + +The test host now matches signing accounts by **SS58 address only**. Previously it also accepted a raw hex public key as the `signer`, which was more lenient than a real wallet and masked a bug in `getLegacyAccountSigner` (it sent the public key as hex instead of SS58). With this change, legacy-account `signRaw`/`signPayload` fail in the test host exactly as they do against a real wallet — so the test suite catches that class of regression. Use a wrapper that sends SS58 signers (`@novasamatech/host-api-wrapper` ≥ 0.8.1). + # host-api-test-sdk 0.9.0 Tracks upstream `@novasamatech/*@^0.8.0` ([triangle-js-sdks#179](https://github.com/paritytech/triangle-js-sdks/pull/179)). v0.8 is **wire-incompatible** with v0.7 — there is no compatibility shim, so your product side must be on `@novasamatech/host-api@^0.8.0` too. The [v0.8 migration guide](https://github.com/paritytech/triangle-js-sdks/blob/release/0.8/docs/migration/v0.8.md) lists all the product-side touchpoints; most products that use `createPapiProvider` for chain access and `@novasamatech/product-react-renderer` for custom chat don't need code changes. diff --git a/package.json b/package.json index f6b821f..d0ebc3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@parity/host-api-test-sdk", - "version": "0.9.0", + "version": "0.9.1", "description": "Lightweight test host for Spektr product E2E testing — embeds dapps with auto-signing dev accounts, no Docker needed", "license": "MIT", "repository": { diff --git a/src/browser/host-runtime.ts b/src/browser/host-runtime.ts index 2331018..b694e9b 100644 --- a/src/browser/host-runtime.ts +++ b/src/browser/host-runtime.ts @@ -245,12 +245,11 @@ function getPairByAddress(address: string): KeyringPair | undefined { for (const pair of pairsByUri.values()) { if (pair.address === address) return pair; } - // Try matching by public key hex (product-sdk sends 0x + hex(publicKey)) - const normalized = address.toLowerCase(); - for (const pair of pairsByUri.values()) { - if (u8aToHex(pair.publicKey).toLowerCase() === normalized) return pair; - } - // Try matching by SS58 re-encoding (address might be in different SS58 format) + // Match by SS58 re-encoding (address might be in a different SS58 prefix). + // NOTE: a raw hex-pubkey fallback was removed intentionally — real wallets + // match by SS58 address only, so the signer field must be a valid SS58 + // address. Keeping the hex fallback here masked the getLegacyAccountSigner + // bug where `signer` was sent as toHex(publicKey). for (const pair of pairsByUri.values()) { try { if (keyring.encodeAddress(pair.publicKey) === address) return pair; diff --git a/test/test-product.ts b/test/test-product.ts index 4fd33f2..d3f91aa 100644 --- a/test/test-product.ts +++ b/test/test-product.ts @@ -118,6 +118,7 @@ async function init() { // Fetch legacy (root) accounts let firstRootAddress: string | null = null; + let firstRootAccount: { publicKey: Uint8Array; name: string | undefined } | null = null; const rootResult = await accountsProvider.getLegacyAccounts(); rootResult.match( @@ -126,6 +127,7 @@ async function init() { rootEl.textContent = JSON.stringify(keys); rootEl.dataset.ready = 'true'; if (keys.length > 0) firstRootAddress = keys[0]; + if (accounts.length > 0) firstRootAccount = accounts[0]; }, () => {}, ); @@ -136,15 +138,17 @@ async function init() { async trySignRaw(): Promise { try { - const r = await hostApi.signRawWithLegacyAccount(enumValue('v1', { - signer: firstRootAddress ?? '', - payload: { tag: 'Bytes' as const, value: new TextEncoder().encode('test-payload') }, - })); - if (r.isOk()) { - const val = r.value; - return { ok: true, signature: val.value.signature }; - } - return { ok: false, error: extractError(r.error) }; + if (!firstRootAccount) return { ok: false, error: 'no legacy account' }; + // Go through the real signer code path (getLegacyAccountSigner -> + // PolkadotSigner.signBytes), which is where the SS58/hex signer + // encoding is decided — rather than hand-building the wire request. + const signer = accountsProvider.getLegacyAccountSigner({ + publicKey: firstRootAccount.publicKey, + dotNsIdentifier: '', + derivationIndex: 0, + }); + const signature = await signer.signBytes(new TextEncoder().encode('test-payload')); + return { ok: true, signature: u8aToHex(signature) }; } catch (err) { return { ok: false, error: extractError(err) }; }