diff --git a/packages/hardware-ledger/src/LedgerKeyAgent.ts b/packages/hardware-ledger/src/LedgerKeyAgent.ts index 28d0f818989..125b6c2bf51 100644 --- a/packages/hardware-ledger/src/LedgerKeyAgent.ts +++ b/packages/hardware-ledger/src/LedgerKeyAgent.ts @@ -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'; @@ -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; @@ -780,6 +810,7 @@ export class LedgerKeyAgent extends KeyAgentBase { } async signCip8Data(request: cip8.Cip8SignDataContext): Promise { + 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(); @@ -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 @@ -809,7 +839,7 @@ export class LedgerKeyAgent extends KeyAgentBase { } : { addressFieldType: MessageAddressFieldType.KEY_HASH, - hashPayload: false, + hashPayload, messageHex: request.payload, preferHexDisplay: false, signingPath @@ -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) { diff --git a/packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts b/packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts index 762075c1817..a409833548e 100644 --- a/packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts +++ b/packages/wallet/test/hardware/ledger/LedgerKeyAgent.test.ts @@ -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 }); @@ -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);