diff --git a/common/changes/@cityofzion/bs-neo3/CU-86ducwq4y_2024-08-14-18-11.json b/common/changes/@cityofzion/bs-neo3/CU-86ducwq4y_2024-08-14-18-11.json new file mode 100644 index 0000000..2eb33b1 --- /dev/null +++ b/common/changes/@cityofzion/bs-neo3/CU-86ducwq4y_2024-08-14-18-11.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@cityofzion/bs-neo3", + "comment": "Fix issue where sending assets from NEON3 is being incorrectly flagged as arbitrary contract invocation", + "type": "patch" + } + ], + "packageName": "@cityofzion/bs-neo3" +} \ No newline at end of file diff --git a/packages/bs-neo3/src/BSNeo3.ts b/packages/bs-neo3/src/BSNeo3.ts index 758be57..227e9e1 100644 --- a/packages/bs-neo3/src/BSNeo3.ts +++ b/packages/bs-neo3/src/BSNeo3.ts @@ -96,7 +96,7 @@ export class BSNeo3 ? u.BigInteger.fromDecimal(intent.amount, intent.tokenDecimals).toString() : intent.amount, }, - { type: 'Any', value: '' }, + { type: 'Any', value: null }, ], } }) diff --git a/packages/bs-neo3/src/NeonDappKitLedgerServiceNeo3.ts b/packages/bs-neo3/src/NeonDappKitLedgerServiceNeo3.ts index c13e0eb..4405d3e 100644 --- a/packages/bs-neo3/src/NeonDappKitLedgerServiceNeo3.ts +++ b/packages/bs-neo3/src/NeonDappKitLedgerServiceNeo3.ts @@ -4,6 +4,20 @@ import { wallet, api, u } from '@cityofzion/neon-js' import { NeonParser } from '@cityofzion/neon-dappkit' import EventEmitter from 'events' +enum LedgerStatus { + OK = 0x9000, +} + +enum LedgerCommand { + GET_PUBLIC_KEY = 0x04, + SIGN = 0x02, +} + +enum LedgerSecondParameter { + MORE_DATA = 0x80, + LAST_DATA = 0x00, +} + export class NeonDappKitLedgerServiceNeo3 implements LedgerService { emitter: LedgerServiceEmitter = new EventEmitter() as LedgerServiceEmitter @@ -39,32 +53,42 @@ export class NeonDappKitLedgerServiceNeo3 implements LedgerService { try { this.emitter.emit('getSignatureStart') - const bip44Buffer = this.toBip44Buffer(addressIndex) - await transport.send(0x80, 0x02, 0, 0x80, bip44Buffer, [0x9000]) - await transport.send(0x80, 0x02, 1, 0x80, Buffer.from(NeonParser.numToHex(networkMagic, 4, true), 'hex'), [ - 0x9000, - ]) + const bip44 = this.#toBip44(addressIndex) + // Send the BIP44 account as first chunk + await this.#sendChunk(transport, LedgerCommand.SIGN, 0, LedgerSecondParameter.MORE_DATA, bip44) + + // Send the network magic as second chunk + await this.#sendChunk( + transport, + LedgerCommand.SIGN, + 1, + LedgerSecondParameter.MORE_DATA, + NeonParser.numToHex(networkMagic, 4, true) + ) const chunks = serializedTransaction.match(/.{1,510}/g) || [] for (let i = 0; i < chunks.length - 1; i++) { - await transport.send(0x80, 0x02, 2 + i, 0x80, Buffer.from(chunks[i], 'hex'), [0x9000]) + // We plus 2 because we already sent 2 chunks before + const commandIndex = 2 + i + await this.#sendChunk(transport, LedgerCommand.SIGN, commandIndex, LedgerSecondParameter.MORE_DATA, chunks[i]) } - const response = await transport.send( - 0x80, - 0x02, - 2 + chunks.length, - 0x00, - Buffer.from(chunks[chunks.length - 1], 'hex'), - [0x9000] + // Again we plus 2 because we already sent 2 chunks before getting the chunks + const lastChunkIndex = 2 + chunks.length + const response = await this.#sendChunk( + transport, + LedgerCommand.SIGN, + lastChunkIndex, + LedgerSecondParameter.LAST_DATA, + chunks[chunks.length - 1] ) if (response.length <= 2) { throw new Error(`No more data but Ledger did not return signature!`) } - const signature = this.derSignatureToHex(response.toString('hex')) + const signature = this.#derSignatureToHex(response.toString('hex')) return signature } finally { @@ -73,28 +97,44 @@ export class NeonDappKitLedgerServiceNeo3 implements LedgerService { } async getPublicKey(transport: Transport, addressIndex = 0): Promise { - const bip44Buffer = this.toBip44Buffer(addressIndex) - - const result = await transport.send(0x80, 0x04, 0x00, 0x00, bip44Buffer, [0x9000]) + const bip44 = this.#toBip44(addressIndex) + + const result = await this.#sendChunk( + transport, + LedgerCommand.GET_PUBLIC_KEY, + 0x00, + LedgerSecondParameter.LAST_DATA, + bip44 + ) const publicKey = result.toString('hex').substring(0, 130) return publicKey } - private toBip44Buffer(addressIndex = 0, changeIndex = 0, accountIndex = 0) { - const accountHex = this.to8BitHex(accountIndex + 0x80000000) - const changeHex = this.to8BitHex(changeIndex) - const addressHex = this.to8BitHex(addressIndex) + #sendChunk( + transport: Transport, + command: LedgerCommand, + commandIndex: number, + secondParameter: LedgerSecondParameter, + chunk: string + ) { + return transport.send(0x80, command, commandIndex, secondParameter, Buffer.from(chunk, 'hex'), [LedgerStatus.OK]) + } + + #toBip44(addressIndex = 0, changeIndex = 0, accountIndex = 0) { + const accountHex = this.#to8BitHex(accountIndex + 0x80000000) + const changeHex = this.#to8BitHex(changeIndex) + const addressHex = this.#to8BitHex(addressIndex) - return Buffer.from('8000002C' + '80000378' + accountHex + changeHex + addressHex, 'hex') + return '8000002C' + '80000378' + accountHex + changeHex + addressHex } - private to8BitHex(num: number): string { + #to8BitHex(num: number): string { const hex = num.toString(16) return '0'.repeat(8 - hex.length) + hex } - private derSignatureToHex(response: string): string { + #derSignatureToHex(response: string): string { const ss = new u.StringStream(response) // The first byte is format. It is usually 0x30 (SEQ) or 0x31 (SET) // The second byte represents the total length of the DER module.