Skip to content

Commit 3b59d0a

Browse files
lace-releases[bot]lace-publish-bot
andauthored
release: lace-extension@2.0.6 (#2225)
Snapshot of input-output-hk/lace-platform @ e3777c8419262d464172454200ad24088a1384de Produced by open-source-release-publish.yml. See PROVENANCE for the allowlist hash that controlled this snapshot. Co-authored-by: lace-publish-bot <lace-publish-bot@users.noreply.github.com>
1 parent c89caec commit 3b59d0a

23 files changed

Lines changed: 466 additions & 90 deletions

PROVENANCE

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
Lace public source snapshot
22
===========================
33

4-
release-tag: lace-extension@2.0.5
4+
release-tag: lace-extension@2.0.6
55
source-repo: input-output-hk/lace-platform (private)
6-
source-commit: 8abaeff5173f18896aee8c5833693240693b4782
7-
snapshot-utc: 2026-05-13T12:49:49.584Z
6+
source-commit: e3777c8419262d464172454200ad24088a1384de
7+
snapshot-utc: 2026-05-22T17:03:46.978Z
88

99
allowlist-sha256: a75a43e9e238d609b21b9d7f3409fa46bb8a0a92a4738dbeba71076ec4e74929
1010
excludelist-sha256: 8e7916a357aae4f8a8a963e32cb48392e039572670b390f043322a9d17e98a50

apps/lace-extension/app.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"slug": "lace-extension",
55
"owner": "lace_io",
66
"newArchEnabled": true,
7-
"version": "2.0.5",
7+
"version": "2.0.6",
88
"orientation": "portrait",
99
"icon": "./assets/icon.png",
1010
"platforms": ["web"],

apps/lace-extension/assets/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "$EXTENSION_NAME",
33
"description": "One fast, accessible, and secure platform for digital assets, DApps, NFTs, and DeFi.",
4-
"version": "2.0.5",
4+
"version": "2.0.6",
55
"manifest_version": 3,
66
"key": "$EXTENSION_KEY",
77
"icons": {

apps/lace-extension/metro.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ const patchNxConfig = nxConfig => {
111111
) {
112112
return { type: 'empty' };
113113
}
114+
// Exclude @sentry/browser's lazyLoadIntegration - it contains a hard-coded
115+
// "https://browser.sentry-cdn.com" string that Chrome Web Store flags as
116+
// remotely hosted code under MV3. Mirror of the webpack stub in
117+
// webpack/sentry-lazy-load-stub.js / base/serviceworker.webpack.config.js.
118+
if (
119+
context.originModulePath.includes('@sentry') &&
120+
moduleName.includes('lazyLoadIntegration')
121+
) {
122+
return { type: 'empty' };
123+
}
114124
// Exclude @effect/platform's HttpApiScalar module - it contains a CDN reference to
115125
// @scalar/api-reference (cdn.jsdelivr.net) that violates Chrome Web Store MV3 policy
116126
// which prohibits remotely hosted code. HttpApiScalar is server-side API docs tooling

packages/module/blockchain-cardano/src/signing/cardano-in-memory-data-signer.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import * as Crypto from '@cardano-sdk/crypto';
2+
import { blake2b } from '@cardano-sdk/crypto';
3+
import { Bip32Account, KeyRole } from '@cardano-sdk/key-management';
14
import { AuthenticationCancelledError } from '@lace-contract/signer';
25
import { from, switchMap, throwError } from 'rxjs';
36

47
import { cip8SignData } from './cip8-sign-data';
58

9+
import type { Ed25519KeyHashHex } from '@cardano-sdk/crypto';
610
import type { AuthSecret } from '@lace-contract/authentication-prompt';
711
import type {
812
CardanoDataSigner,
@@ -39,6 +43,7 @@ export class CardanoInMemoryDataSigner implements CardanoDataSigner {
3943
authSecret: AuthSecret,
4044
request: CardanoSignDataRequest,
4145
): Promise<CardanoSignDataResult> {
46+
const dRepKeyHash = await this.#deriveDRepKeyHash();
4247
const keyAgent = await this.#props.createKeyAgent({
4348
accountIndex: this.#props.accountIndex,
4449
chainId: this.#props.chainId,
@@ -47,6 +52,28 @@ export class CardanoInMemoryDataSigner implements CardanoDataSigner {
4752
authSecret,
4853
});
4954

50-
return cip8SignData(keyAgent, request, this.#props.knownAddresses);
55+
return cip8SignData({
56+
keyAgent,
57+
request,
58+
knownAddresses: this.#props.knownAddresses,
59+
dRepKeyHash,
60+
});
61+
}
62+
63+
async #deriveDRepKeyHash(): Promise<Ed25519KeyHashHex> {
64+
const bip32Ed25519 = await Crypto.SodiumBip32Ed25519.create();
65+
const bip32Account = new Bip32Account(
66+
{
67+
accountIndex: this.#props.accountIndex,
68+
chainId: this.#props.chainId,
69+
extendedAccountPublicKey: this.#props.extendedAccountPublicKey,
70+
},
71+
{ blake2b, bip32Ed25519 },
72+
);
73+
const dRepPubKey = await bip32Account.derivePublicKey({
74+
index: 0,
75+
role: KeyRole.DRep,
76+
});
77+
return Crypto.Ed25519PublicKey.fromHex(dRepPubKey).hash().hex();
5178
}
5279
}

packages/module/blockchain-cardano/src/signing/cip8-sign-data.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Cardano, Serialization } from '@cardano-sdk/core';
22
import { HexBlob } from '@cardano-sdk/util';
33
import { HexBytes } from '@lace-sdk/util';
44

5+
import type { Ed25519KeyHashHex } from '@cardano-sdk/crypto';
56
import type { GroupedAddress } from '@cardano-sdk/key-management';
67
import type { CardanoKeyAgent } from '@lace-contract/cardano-context';
78
import type {
@@ -18,11 +19,21 @@ const COSE_KEY_ALG = 3;
1819
const COSE_KEY_CRV = -1;
1920
const COSE_KEY_X = -2;
2021
const STAKE_KEY_DERIVATION_PATH = { role: 2, index: 0 };
22+
const DREP_KEY_DERIVATION_PATH = { role: 3, index: 0 };
2123

22-
const getDerivationPath = (
23-
signWith: Cardano.PaymentAddress | Cardano.RewardAccount,
24-
knownAddresses: GroupedAddress[],
25-
): { role: number; index: number } => {
24+
interface GetDerivationPathParams {
25+
address: Cardano.Address;
26+
signWith: Cardano.PaymentAddress | Cardano.RewardAccount;
27+
knownAddresses: GroupedAddress[];
28+
dRepKeyHash: Ed25519KeyHashHex | undefined;
29+
}
30+
31+
const getDerivationPath = ({
32+
address,
33+
signWith,
34+
knownAddresses,
35+
dRepKeyHash,
36+
}: GetDerivationPathParams): { role: number; index: number } => {
2637
if (Cardano.isRewardAccount(signWith)) {
2738
const matchingAddress = knownAddresses.find(
2839
addr => addr.rewardAccount === signWith,
@@ -36,6 +47,14 @@ const getDerivationPath = (
3647
return STAKE_KEY_DERIVATION_PATH;
3748
}
3849

50+
if (
51+
dRepKeyHash &&
52+
address.getType() === Cardano.AddressType.EnterpriseKey &&
53+
address.getProps().paymentPart?.hash === dRepKeyHash
54+
) {
55+
return DREP_KEY_DERIVATION_PATH;
56+
}
57+
3958
const matchingAddress = knownAddresses.find(
4059
addr => addr.address === signWith,
4160
);
@@ -46,7 +65,7 @@ const getDerivationPath = (
4665
};
4766
}
4867

49-
return { role: 0, index: 0 };
68+
throw new Error(`Unknown signWith address: ${signWith}`);
5069
};
5170

5271
const createProtectedHeaders = (addressBytes: Uint8Array): Uint8Array => {
@@ -109,19 +128,32 @@ const createCoseKey = (
109128
return writer.encode();
110129
};
111130

131+
export interface Cip8SignDataParams {
132+
keyAgent: CardanoKeyAgent;
133+
request: CardanoSignDataRequest;
134+
knownAddresses: GroupedAddress[];
135+
dRepKeyHash?: Ed25519KeyHashHex;
136+
}
137+
112138
/** Signs data per CIP-8 by constructing COSE structures without WASM. */
113-
export const cip8SignData = async (
114-
keyAgent: CardanoKeyAgent,
115-
request: CardanoSignDataRequest,
116-
knownAddresses: GroupedAddress[],
117-
): Promise<CardanoSignDataResult> => {
139+
export const cip8SignData = async ({
140+
keyAgent,
141+
request,
142+
knownAddresses,
143+
dRepKeyHash,
144+
}: Cip8SignDataParams): Promise<CardanoSignDataResult> => {
118145
const address = Cardano.Address.fromString(request.signWith);
119146
if (!address) {
120147
throw new Error(`Invalid address: ${request.signWith}`);
121148
}
122149

123150
const addressBytes = Buffer.from(address.toBytes(), 'hex');
124-
const derivationPath = getDerivationPath(request.signWith, knownAddresses);
151+
const derivationPath = getDerivationPath({
152+
address,
153+
signWith: request.signWith,
154+
knownAddresses,
155+
dRepKeyHash,
156+
});
125157

126158
const protectedHeadersBytes = createProtectedHeaders(addressBytes);
127159
const payloadBytes = Buffer.from(request.payload, 'hex');

packages/module/blockchain-cardano/test/signing/cardano-in-memory-data-signer.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,38 @@ import type {
1414
import type { SignerAuth } from '@lace-contract/signer';
1515
import type { HexBytes } from '@lace-sdk/util';
1616

17+
const STUB_DREP_PUB_KEY = 'ab'.repeat(32);
18+
const STUB_DREP_KEY_HASH = 'cd'.repeat(28);
19+
1720
vi.mock('../../src/signing/cip8-sign-data', () => ({
1821
cip8SignData: vi
1922
.fn()
2023
.mockResolvedValue({ signature: 'sig-hex', key: 'key-hex' }),
2124
}));
2225

26+
vi.mock('@cardano-sdk/key-management', async () => {
27+
const actual = await vi.importActual('@cardano-sdk/key-management');
28+
return {
29+
...actual,
30+
Bip32Account: vi.fn().mockImplementation(() => ({
31+
derivePublicKey: vi.fn().mockResolvedValue(STUB_DREP_PUB_KEY),
32+
})),
33+
};
34+
});
35+
36+
vi.mock('@cardano-sdk/crypto', async () => {
37+
const actual = await vi.importActual('@cardano-sdk/crypto');
38+
return {
39+
...actual,
40+
SodiumBip32Ed25519: { create: vi.fn().mockResolvedValue({}) },
41+
Ed25519PublicKey: {
42+
fromHex: vi.fn().mockReturnValue({
43+
hash: () => ({ hex: () => STUB_DREP_KEY_HASH }),
44+
}),
45+
},
46+
};
47+
});
48+
2349
const mockAuthSecret = new Uint8Array([1, 2, 3]);
2450

2551
const createMockAuth = (authSecret: Uint8Array): SignerAuth => ({
@@ -73,6 +99,43 @@ describe('CardanoInMemoryDataSigner', () => {
7399
expect(result).toEqual({ signature: 'sig-hex', key: 'key-hex' });
74100
});
75101

102+
it('pre-derives the DRep key hash and forwards it to cip8SignData (LW-14940)', async () => {
103+
const { cip8SignData } = await import('../../src/signing/cip8-sign-data');
104+
vi.mocked(cip8SignData).mockClear();
105+
106+
const mockKeyAgent: CardanoKeyAgent = {
107+
signTransaction: vi.fn(),
108+
signBlob: vi.fn(),
109+
};
110+
const createKeyAgent: CreateCardanoKeyAgent = vi
111+
.fn()
112+
.mockResolvedValue(mockKeyAgent);
113+
114+
const knownAddresses = [{ address: 'addr_test1...' }] as never;
115+
const signer = new CardanoInMemoryDataSigner({
116+
...mockProps,
117+
createKeyAgent,
118+
knownAddresses,
119+
});
120+
121+
await firstValueFrom(
122+
signer.signData({
123+
signWith: 'addr_test1qz...' as Cardano.PaymentAddress,
124+
payload: 'deadbeef',
125+
}),
126+
);
127+
128+
expect(cip8SignData).toHaveBeenCalledWith({
129+
keyAgent: mockKeyAgent,
130+
request: {
131+
signWith: 'addr_test1qz...' as Cardano.PaymentAddress,
132+
payload: 'deadbeef',
133+
},
134+
knownAddresses,
135+
dRepKeyHash: STUB_DREP_KEY_HASH,
136+
});
137+
});
138+
76139
it('propagates signing errors', async () => {
77140
const { cip8SignData } = await import('../../src/signing/cip8-sign-data');
78141
vi.mocked(cip8SignData).mockRejectedValueOnce(

0 commit comments

Comments
 (0)