Skip to content

Commit

Permalink
test(e2e): test signData with drepID
Browse files Browse the repository at this point in the history
  • Loading branch information
mirceahasegan committed Dec 20, 2024
1 parent 44c3716 commit 99a5930
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 63 deletions.
1 change: 1 addition & 0 deletions packages/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"@cardano-sdk/util-rxjs": "workspace:~",
"@cardano-sdk/wallet": "workspace:~",
"@dcspark/cardano-multiplatform-lib-nodejs": "^3.1.1",
"@emurgo/cardano-message-signing-nodejs": "^1.0.1",
"@shiroyasha9/axios-fetch-adapter": "1.0.3",
"axios": "^1.7.4",
"bunyan": "^1.8.15",
Expand Down
119 changes: 119 additions & 0 deletions packages/e2e/test/wallet_epoch_0/PersonalWallet/cip30WalletApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as Crypto from '@cardano-sdk/crypto';
import { BaseWallet, cip30 } from '@cardano-sdk/wallet';
import { Bip32Account, KeyRole, cip8 } from '@cardano-sdk/key-management';
import { COSEKey, COSESign1 } from '@emurgo/cardano-message-signing-nodejs';
import { Cardano, util } from '@cardano-sdk/core';
import { Cip30DataSignature, SenderContext } from '@cardano-sdk/dapp-connector';
import { HexBlob } from '@cardano-sdk/util';
import { NEVER, firstValueFrom, of } from 'rxjs';
import { buildDRepAddressFromDRepKey } from '../../../../wallet/test/util';
import { getEnv, getWallet, walletReady, walletVariables } from '../../../src';
import { logger } from '@cardano-sdk/util-dev';

const env = getEnv(walletVariables);

const decodeSignature = (dataSignature: Cip30DataSignature) => {
const coseKey = COSEKey.from_bytes(Buffer.from(dataSignature.key, 'hex'));
const coseSign1 = COSESign1.from_bytes(Buffer.from(dataSignature.signature, 'hex'));

const publicKeyHeader = coseKey.header(cip8.CoseLabel.x)!;
const publicKeyBytes = publicKeyHeader.as_bytes()!;
const publicKeyHex = util.bytesToHex(publicKeyBytes);
const signedData = coseSign1.signed_data();
return { coseKey, coseSign1, publicKeyHex, signedData };
};

describe('PersonalWallet/cip30WalletApi', () => {
let wallet: BaseWallet;
let drepKeyHashHex: Crypto.Ed25519KeyHashHex;
let drepPubKey: Crypto.Ed25519PublicKeyHex;
let walletApi: ReturnType<typeof cip30.createWalletApi>;
let bip32Account: Bip32Account;

beforeEach(async () => {
({ wallet, bip32Account } = await getWallet({ env, logger, name: 'wallet' }));
await walletReady(wallet, 10n);

drepPubKey = (await wallet.governance.getPubDRepKey())!;
drepKeyHashHex = (await Crypto.Ed25519PublicKey.fromHex(drepPubKey!).hash()).hex();

walletApi = cip30.createWalletApi(
of(wallet),
{
signData: () => Promise.resolve({ cancel$: NEVER })
} as unknown as cip30.CallbackConfirmation,
{ logger: console }
);
});

it('can signData with hex DRepID', async () => {
const signature = await walletApi.signData(
{ sender: '' } as unknown as SenderContext,
drepKeyHashHex,
HexBlob('abc123')
);

expect(decodeSignature(signature).publicKeyHex).toEqual(drepPubKey);
});

it('can signData with bech32 type 6 addr DRepID', async () => {
const drepAddr = (await buildDRepAddressFromDRepKey(drepPubKey))?.toAddress()?.toBech32();
const signature = await walletApi.signData(
{ sender: '' } as unknown as SenderContext,
drepAddr!,
HexBlob('abc123')
);

expect(decodeSignature(signature).publicKeyHex).toEqual(drepPubKey);
});

it('can signData with bech32 base address', async () => {
const [{ address, index }] = await firstValueFrom(wallet.addresses$);
const paymentKeyHex = (await bip32Account.derivePublicKey({ index, role: KeyRole.External })).hex();

const signature = await walletApi.signData({ sender: '' } as unknown as SenderContext, address, HexBlob('abc123'));

expect(decodeSignature(signature).publicKeyHex).toEqual(paymentKeyHex);
});

it('can signData with hex-encoded base address', async () => {
const [{ address, index }] = await firstValueFrom(wallet.addresses$);
const addressHex = Cardano.Address.fromBech32(address).toBytes();
const paymentKeyHex = (await bip32Account.derivePublicKey({ index, role: KeyRole.External })).hex();

const signature = await walletApi.signData(
{ sender: '' } as unknown as SenderContext,
addressHex,
HexBlob('abc123')
);

expect(decodeSignature(signature).publicKeyHex).toEqual(paymentKeyHex);
});

it('can signData with bech32 base address', async () => {
const [{ rewardAccount, index }] = await firstValueFrom(wallet.addresses$);
const stakeKeyHex = (await bip32Account.derivePublicKey({ index, role: KeyRole.Stake })).hex();

const signature = await walletApi.signData(
{ sender: '' } as unknown as SenderContext,
rewardAccount,
HexBlob('abc123')
);

expect(decodeSignature(signature).publicKeyHex).toEqual(stakeKeyHex);
});

it('can signData with hex-encoded reward account', async () => {
const [{ rewardAccount, index }] = await firstValueFrom(wallet.addresses$);
const rewardAccountHex = Cardano.Address.fromBech32(rewardAccount).toBytes();
const stakeKeyHex = (await bip32Account.derivePublicKey({ index, role: KeyRole.Stake })).hex();

const signature = await walletApi.signData(
{ sender: '' } as unknown as SenderContext,
rewardAccountHex,
HexBlob('abc123')
);

expect(decodeSignature(signature).publicKeyHex).toEqual(stakeKeyHex);
});
});
2 changes: 0 additions & 2 deletions packages/e2e/test/web-extension/extension/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ export const selectors = {
btnDelegate: '#multiDelegation .delegate button',
btnGrantAccess: '#requestAccessGrant',
btnSignAndBuildTx: '#buildAndSignTx',
btnSignDataWithDRepId: '#signDataWithDRepId',
deactivateWallet: '#deactivateWallet',
destroyWallet: '#destroyWallet',
divAdaPrice: '#adaPrice',
divBgPortDisconnectStatus: '#remoteApiPortDisconnect .bgPortDisconnect',
divDataSignature: '#dataSignature',
divSignature: '#signature',
divUiPortDisconnectStatus: '#remoteApiPortDisconnect .uiPortDisconnect',
liPercents: '#multiDelegation .distribution li .percent',
Expand Down
2 changes: 0 additions & 2 deletions packages/e2e/test/web-extension/extension/ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ <h3>Delegation distribution:</h3>
<button id="buildAndSignTx">Build & Sign TX</button>
<div>Signature: <span id="signature">-</span></div>

<button id="signDataWithDRepId">Sign Data with DRepID</button>
<div>Signature: <span id="dataSignature">-</span></div>
<script src="ui.js"></script>
</body>

Expand Down
21 changes: 0 additions & 21 deletions packages/e2e/test/web-extension/extension/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,23 +124,6 @@ const sendDelegationTx = async (portfolio: { pool: Cardano.StakePool; weight: nu
document.querySelector('#multiDelegation .delegateTxId')!.textContent = msg;
};

const signDataWithDRepID = async (): Promise<void> => {
let msg: string;
const dRepId = 'drep1vpzcgfrlgdh4fft0p0ju70czkxxkuknw0jjztl3x7aqgm9q3hqyaz';
try {
const signature = await wallet.signData({
payload: HexBlob('abc123'),
signWith: Cardano.DRepID(dRepId)
});
msg = JSON.stringify(signature);
} catch (error) {
msg = `ERROR signing data with DRepID: ${JSON.stringify(error)}`;
}

// Set text with signature or error
document.querySelector(selectors.divDataSignature)!.textContent = msg;
};

const setAddresses = ({ address, stakeAddress }: { address: string; stakeAddress: string }): void => {
document.querySelector(selectors.spanAddress)!.textContent = address;
document.querySelector(selectors.spanStakeAddress)!.textContent = stakeAddress;
Expand Down Expand Up @@ -378,10 +361,6 @@ document.querySelector(selectors.btnSignAndBuildTx)!.addEventListener('click', a
setSignature(signedTx.witness.signatures.values().next().value);
});

document
.querySelector(selectors.btnSignDataWithDRepId)!
.addEventListener('click', async () => await signDataWithDRepID());

// Code below tests that a disconnected port in background script will result in the consumed API method call promise to reject
// UI consumes API -> BG exposes fake API that closes port
const disconnectPortTestObj = consumeRemoteApi(
Expand Down
9 changes: 1 addition & 8 deletions packages/e2e/test/web-extension/specs/wallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ describe('wallet', () => {
liPools,
liPercents,
divBgPortDisconnectStatus,
divUiPortDisconnectStatus,
btnSignDataWithDRepId,
divDataSignature
divUiPortDisconnectStatus
} = selectors;

// The address is filled in by the tests, which are order dependent
Expand Down Expand Up @@ -137,11 +135,6 @@ describe('wallet', () => {
await buildAndSign();
});

it('can sign data with a DRepID', async () => {
await (await $(btnSignDataWithDRepId)).click();
await expect($(divDataSignature)).toHaveTextContaining('signature');
});

it('can destroy second wallet before switching back to the first wallet', async () => {
// Destroy also clears associated store. Store will be rebuilt during future activation of same wallet
await $(destroyWallet).click();
Expand Down
82 changes: 52 additions & 30 deletions packages/hardware-ledger/src/LedgerKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,26 +209,17 @@ type OpenTransportForDeviceParams = {
device: LedgerDevice;
};

const isPaymentAddress = (
signWith: Cardano.PaymentAddress | Cardano.RewardAccount
): signWith is Cardano.PaymentAddress => signWith.startsWith('addr');

const getDerivationPath = (
signWith: Cardano.PaymentAddress | Cardano.RewardAccount,
knownAddresses: GroupedAddress[],
accountIndex: number,
purpose: number
): { signingPath: BIP32Path; addressParams: DeviceOwnedAddress } => {
if (Cardano.DRepID.isValid(signWith)) {
const path = util.accountKeyDerivationPathToBip32Path(accountIndex, util.DREP_KEY_DERIVATION_PATH, purpose);

return {
addressParams: {
params: {
spendingPath: path
},
type: AddressType.ENTERPRISE_KEY
},
signingPath: path
};
}

purpose: number,
dRepKeyHash: Crypto.Ed25519KeyHashHex
): { signingPath: BIP32Path; addressParams?: DeviceOwnedAddress; addressFieldType: MessageAddressFieldType } => {
const isRewardAccount = signWith.startsWith('stake');

// Reward account
Expand All @@ -245,6 +236,7 @@ const getDerivationPath = (
);

return {
addressFieldType: MessageAddressFieldType.ADDRESS,
addressParams: {
params: {
stakingPath: path
Expand All @@ -255,6 +247,20 @@ const getDerivationPath = (
};
}

if (isPaymentAddress(signWith)) {
const drepAddr = Cardano.Address.fromString(signWith);
if (
drepAddr?.getType() === Cardano.AddressType.EnterpriseKey &&
drepAddr?.getProps().paymentPart?.hash === Crypto.Hash28ByteBase16.fromEd25519KeyHashHex(dRepKeyHash)
) {
const path = util.accountKeyDerivationPathToBip32Path(accountIndex, util.DREP_KEY_DERIVATION_PATH, purpose);
return {
addressFieldType: MessageAddressFieldType.KEY_HASH,
signingPath: path
};
}
}

const knownAddress = knownAddresses.find(({ address }) => address === signWith);

if (!knownAddress) {
Expand Down Expand Up @@ -282,6 +288,7 @@ const getDerivationPath = (
);

return {
addressFieldType: MessageAddressFieldType.ADDRESS,
addressParams: {
params: {
spendingPath,
Expand All @@ -304,6 +311,7 @@ const getDerivationPath = (

// Enterprise Address
return {
addressFieldType: MessageAddressFieldType.ADDRESS,
addressParams: {
params: {
spendingPath
Expand Down Expand Up @@ -746,25 +754,39 @@ export class LedgerKeyAgent extends KeyAgentBase {

async signCip8Data(request: cip8.Cip8SignDataContext): Promise<Cip30DataSignature> {
try {
const { signingPath, addressParams } = getDerivationPath(
const dRepPublicKey = await this.derivePublicKey(util.DREP_KEY_DERIVATION_PATH);
const dRepKeyHashHex = (await Crypto.Ed25519PublicKey.fromHex(dRepPublicKey).hash()).hex();

const { signingPath, addressParams, addressFieldType } = getDerivationPath(
request.signWith,
request.knownAddresses,
this.accountIndex,
this.purpose
this.purpose,
dRepKeyHashHex
);

const messageData: MessageData = {
address: addressParams,
addressFieldType: MessageAddressFieldType.ADDRESS,
hashPayload: false,
messageHex: request.payload,
network: {
networkId: this.chainId.networkId,
protocolMagic: this.chainId.networkMagic
},
preferHexDisplay: false,
signingPath
};
const messageData: MessageData =
addressFieldType === MessageAddressFieldType.ADDRESS && addressParams
? {
address: addressParams,
addressFieldType: MessageAddressFieldType.ADDRESS,
hashPayload: false,
messageHex: request.payload,

network: {
networkId: this.chainId.networkId,
protocolMagic: this.chainId.networkMagic
},
preferHexDisplay: false,
signingPath
}
: {
addressFieldType: MessageAddressFieldType.KEY_HASH,
hashPayload: false,
messageHex: request.payload,
preferHexDisplay: false,
signingPath
};

const deviceConnection = await LedgerKeyAgent.checkDeviceConnection(
this.#communicationType,
Expand Down
20 changes: 20 additions & 0 deletions packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Hash32ByteBase16 } from '@cardano-sdk/crypto';
import { HexBlob } from '@cardano-sdk/util';
import { InitializeTxProps, InitializeTxResult } from '@cardano-sdk/tx-construction';
import { LedgerKeyAgent, LedgerTransportType } from '@cardano-sdk/hardware-ledger';
import { buildDRepAddressFromDRepKey } from '../../util';
import { firstValueFrom } from 'rxjs';
import { getDevices } from '@ledgerhq/hw-transport-node-hid-noevents';
import { dummyLogger as logger } from 'ts-log';
Expand Down Expand Up @@ -747,6 +748,25 @@ describe('LedgerKeyAgent', () => {
)
).toBe(true);
});

it('can sign with drep key', async () => {
const drepPubKey = await wallet.governance.getPubDRepKey();
const signWith = (await buildDRepAddressFromDRepKey(drepPubKey!))?.toAddress()?.toBech32();
const { coseSign1, publicKeyHex, signedData } = await signAndDecode(signWith!, wallet);
const signedDataBytes = HexBlob.fromBytes(signedData.to_bytes());
const signatureBytes = HexBlob.fromBytes(coseSign1.signature()) as unknown as Crypto.Ed25519SignatureHex;
const cryptoProvider = new Crypto.SodiumBip32Ed25519();

expect(publicKeyHex).toEqual(drepPubKey);

expect(
await cryptoProvider.verify(
signatureBytes,
signedDataBytes,
publicKeyHex as unknown as Crypto.Ed25519PublicKeyHex
)
).toBe(true);
});
});
});
});
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3626,6 +3626,7 @@ __metadata:
"@dcspark/cardano-multiplatform-lib-browser": ^3.1.1
"@dcspark/cardano-multiplatform-lib-nodejs": ^3.1.1
"@emurgo/cardano-message-signing-asmjs": ^1.0.1
"@emurgo/cardano-message-signing-nodejs": ^1.0.1
"@shiroyasha9/axios-fetch-adapter": 1.0.3
"@types/bunyan": ^1.8.8
"@types/chalk": ^2.2.0
Expand Down

0 comments on commit 99a5930

Please sign in to comment.