Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions packages/hardware-ledger/src/LedgerKeyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { Cip30DataSignature } from '@cardano-sdk/dapp-connector';
import { HID } from 'node-hid';
import { HexBlob, areNumbersEqualInConstantTime, areStringsEqualInConstantTime } from '@cardano-sdk/util';
import { LedgerDevice, LedgerTransportType } from './types';
import { blake2b } from '@cardano-sdk/crypto';
import { getFirstLedgerDevice } from '@ledgerhq/hw-transport-webusb/lib/webusb';
import { str_to_path } from '@cardano-foundation/ledgerjs-hw-app-cardano/dist/utils/address';
import { toLedgerTx } from './transformers';
Expand Down Expand Up @@ -66,9 +67,38 @@ const LedgerConnection = (_LedgerConnection as any).default
: _LedgerConnection;
type LedgerConnection = _LedgerConnection;

const CIP08_SIGN_HASH_THRESHOLD = 100;

const isUsbDevice = (device: any): device is USBDevice =>
typeof USBDevice !== 'undefined' && device instanceof USBDevice;

/* Sets the hashed entry in the COSESign1 CBOR structure */
const setCOSESignHashed = (COSESignCbor: string, isHashed: boolean) => {
const reader = new Serialization.CborReader(HexBlob(COSESignCbor));
reader.readStartArray();

const headers = reader.readEncodedValue();
// Skip hashed entry
reader.readEncodedValue();
const payload = reader.readEncodedValue();
const signature = reader.readEncodedValue();

reader.readEndArray();

const writer = new Serialization.CborWriter();

writer.writeStartArray(4);
writer.writeEncodedValue(headers);
writer.writeStartMap(1);
writer.writeTextString('hashed');
writer.writeBoolean(isHashed);

writer.writeEncodedValue(payload);
writer.writeEncodedValue(signature);

return writer.encodeAsHex();
};

const isDeviceAlreadyOpenError = (error: unknown) => {
if (typeof error !== 'object') return false;
const innerError = (error as any).innerError;
Expand Down Expand Up @@ -780,6 +810,7 @@ export class LedgerKeyAgent extends KeyAgentBase {
}

async signCip8Data(request: cip8.Cip8SignDataContext): Promise<Cip30DataSignature> {
const hashPayload = request.payload.length >= CIP08_SIGN_HASH_THRESHOLD;
try {
const dRepPublicKey = await this.derivePublicKey(util.DREP_KEY_DERIVATION_PATH);
const dRepKeyHashHex = (await Crypto.Ed25519PublicKey.fromHex(dRepPublicKey).hash()).hex();
Expand All @@ -797,9 +828,8 @@ export class LedgerKeyAgent extends KeyAgentBase {
? {
address: addressParams,
addressFieldType: MessageAddressFieldType.ADDRESS,
hashPayload: false,
hashPayload,
messageHex: request.payload,

network: {
networkId: this.chainId.networkId,
protocolMagic: this.chainId.networkMagic
Expand All @@ -809,7 +839,7 @@ export class LedgerKeyAgent extends KeyAgentBase {
}
: {
addressFieldType: MessageAddressFieldType.KEY_HASH,
hashPayload: false,
hashPayload,
messageHex: request.payload,
preferHexDisplay: false,
signingPath
Expand All @@ -830,18 +860,20 @@ export class LedgerKeyAgent extends KeyAgentBase {
protectedHeaders.set_algorithm_id(Label.from_algorithm_id(AlgorithmId.EdDSA));
protectedHeaders.set_header(cip8.CoseLabel.address, CBORValue.new_bytes(addressBytes));

const sigPayload = coreUtils.hexToBytes(hashPayload ? blake2b.hash(request.payload, 28) : request.payload);
const builder = COSESign1Builder.new(
Headers.new(ProtectedHeaderMap.new(protectedHeaders), HeaderMap.new()),
coreUtils.hexToBytes(request.payload),
sigPayload,
false
);

const coseSign1 = builder.build(Buffer.from(result.signatureHex, 'hex'));
const coseKey = cip8.createCoseKey(addressBytes, Crypto.Ed25519PublicKeyHex(result.signingPublicKeyHex));

const coseSigHex = coreUtils.bytesToHex(coseSign1.to_bytes());
return {
key: coreUtils.bytesToHex(coseKey.to_bytes()),
signature: coreUtils.bytesToHex(coseSign1.to_bytes())
signature: hashPayload ? setCOSESignHashed(coseSigHex, true) : coseSigHex
};
} catch (error: any) {
if (error.code === 28_169) {
Expand Down
32 changes: 30 additions & 2 deletions packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ const cleanupEstablishedConnections = async () => {
LedgerKeyAgent.deviceConnections = [];
};

const signAndDecode = async (signWith: Cardano.PaymentAddress | Cardano.RewardAccount, wallet: BaseWallet) => {
const signAndDecode = async (
signWith: Cardano.PaymentAddress | Cardano.RewardAccount,
wallet: BaseWallet,
message = HexBlob('abc123')
) => {
const dataSignature = await wallet.signData({
payload: HexBlob('abc123'),
payload: message,
signWith
});

Expand Down Expand Up @@ -799,6 +803,30 @@ describe('LedgerKeyAgent', () => {
});

describe('CIP-008 Messages', () => {
it('can sign a long message', async () => {
const message = HexBlob(
Buffer.from(
'STAR 00000000000 to addr_test1qpj0zrza6l4caj9q4yfnvugcfum5rls7tkzvezzatp488tev0tc8arve599qvp6r28hsf6tm0cfdm3f70u58rjuycgtsrypwh5 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
'ascii'
).toString('hex')
);
const signWith = (await firstValueFrom(wallet.addresses$))[0].address;
const { coseSign1, publicKeyHex, signedData } = await signAndDecode(signWith as any, wallet, message);
const signedDataBytes = HexBlob.fromBytes(signedData.to_bytes());
const signatureBytes = HexBlob.fromBytes(coseSign1.signature()) as unknown as Crypto.Ed25519SignatureHex;
const cryptoProvider = await Crypto.SodiumBip32Ed25519.create();

testAddressHeader(signedData, signWith);

expect(
cryptoProvider.verify(
signatureBytes,
signedDataBytes,
publicKeyHex as unknown as Crypto.Ed25519PublicKeyHex
)
).toBe(true);
});

it('can sign with reward account', async () => {
const signWith = (await firstValueFrom(wallet.addresses$))[0].rewardAccount;
const { coseSign1, publicKeyHex, signedData } = await signAndDecode(signWith, wallet);
Expand Down
Loading