Skip to content

Commit

Permalink
CU-86dtvue77 - Fixes for LedgerService
Browse files Browse the repository at this point in the history
  • Loading branch information
raulduartep committed Jun 20, 2024
1 parent 475e4cf commit b4ac988
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@cityofzion/bs-ethereum",
"comment": "Emit \"getSignatureEnd\" when throws an error and fix errors when try to invoke some methods",
"type": "patch"
}
],
"packageName": "@cityofzion/bs-ethereum"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@cityofzion/bs-neo3",
"comment": "Emit \"getSignatureEnd\" when throws an error",
"type": "patch"
}
],
"packageName": "@cityofzion/bs-neo3"
}
135 changes: 88 additions & 47 deletions packages/bs-ethereum/src/LedgerServiceEthereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,80 +58,121 @@ export class LedgerSigner extends Signer implements TypedDataSigner {
}

async signMessage(message: string | ethers.utils.Bytes): Promise<string> {
if (typeof message === 'string') {
message = ethers.utils.toUtf8Bytes(message)
}
try {
if (typeof message === 'string') {
message = ethers.utils.toUtf8Bytes(message)
}

const ledgerApp = new LedgerEthereumApp(this.#transport)
const ledgerApp = new LedgerEthereumApp(this.#transport)

this.#emitter?.emit('getSignatureStart')
this.#emitter?.emit('getSignatureStart')

const obj = await this.#retry(() =>
ledgerApp.signPersonalMessage(this.#path, ethers.utils.hexlify(message).substring(2))
)
const obj = await this.#retry(() =>
ledgerApp.signPersonalMessage(this.#path, ethers.utils.hexlify(message).substring(2))
)

this.#emitter?.emit('getSignatureEnd')
this.#emitter?.emit('getSignatureEnd')

// Normalize the signature for Ethers
obj.r = '0x' + obj.r
obj.s = '0x' + obj.s
// Normalize the signature for Ethers
obj.r = '0x' + obj.r
obj.s = '0x' + obj.s

return ethers.utils.joinSignature(obj)
return ethers.utils.joinSignature(obj)
} catch (error) {
this.#emitter?.emit('getSignatureEnd')
throw error
}
}

async signTransaction(transaction: ethers.utils.Deferrable<ethers.providers.TransactionRequest>): Promise<string> {
const ledgerApp = new LedgerEthereumApp(this.#transport)

const tx = await ethers.utils.resolveProperties(transaction)
const unsignedTransaction: ethers.utils.UnsignedTransaction = {
...tx,
nonce: tx.nonce ? ethers.BigNumber.from(transaction.nonce).toNumber() : undefined,
}
try {
const ledgerApp = new LedgerEthereumApp(this.#transport)

const tx = await ethers.utils.resolveProperties(transaction)
const unsignedTransaction: ethers.utils.UnsignedTransaction = {
chainId: tx.chainId ?? undefined,
data: tx.data ?? undefined,
gasLimit: tx.gasLimit ?? undefined,
gasPrice: tx.gasPrice ?? undefined,
nonce: tx.nonce ? ethers.BigNumber.from(tx.nonce).toNumber() : undefined,
to: tx.to ?? undefined,
value: tx.value ?? undefined,
}

const serializedUnsignedTransaction = ethers.utils.serializeTransaction(unsignedTransaction).substring(2)
const serializedUnsignedTransaction = ethers.utils.serializeTransaction(unsignedTransaction).substring(2)

const resolution = await LedgerEthereumAppService.resolveTransaction(serializedUnsignedTransaction, {}, {})
const resolution = await LedgerEthereumAppService.resolveTransaction(serializedUnsignedTransaction, {}, {})

this.#emitter?.emit('getSignatureStart')
this.#emitter?.emit('getSignatureStart')

const signature = await this.#retry(() =>
ledgerApp.signTransaction(this.#path, serializedUnsignedTransaction, resolution)
)
const signature = await this.#retry(() =>
ledgerApp.signTransaction(this.#path, serializedUnsignedTransaction, resolution)
)

this.#emitter?.emit('getSignatureEnd')
this.#emitter?.emit('getSignatureEnd')

return ethers.utils.serializeTransaction(unsignedTransaction, {
v: ethers.BigNumber.from('0x' + signature.v).toNumber(),
r: '0x' + signature.r,
s: '0x' + signature.s,
})
return ethers.utils.serializeTransaction(unsignedTransaction, {
v: ethers.BigNumber.from('0x' + signature.v).toNumber(),
r: '0x' + signature.r,
s: '0x' + signature.s,
})
} catch (error) {
this.#emitter?.emit('getSignatureEnd')
throw error
}
}

async _signTypedData(
domain: ethers.TypedDataDomain,
types: Record<string, ethers.TypedDataField[]>,
value: Record<string, any>
): Promise<string> {
const populated = await ethers.utils._TypedDataEncoder.resolveNames(domain, types, value, async (name: string) => {
if (!this.provider) throw new Error('Cannot resolve ENS names without a provider')
const resolved = await this.provider.resolveName(name)
if (!resolved) throw new Error('No address found for domain name')
return resolved
})
try {
const populated = await ethers.utils._TypedDataEncoder.resolveNames(
domain,
types,
value,
async (name: string) => {
if (!this.provider) throw new Error('Cannot resolve ENS names without a provider')
const resolved = await this.provider.resolveName(name)
if (!resolved) throw new Error('No address found for domain name')
return resolved
}
)

const payload = ethers.utils._TypedDataEncoder.getPayload(populated.domain, types, populated.value)
const payload = ethers.utils._TypedDataEncoder.getPayload(populated.domain, types, populated.value)

const ledgerApp = new LedgerEthereumApp(this.#transport)
const ledgerApp = new LedgerEthereumApp(this.#transport)

this.#emitter?.emit('getSignatureStart')
const obj = await this.#retry(() => ledgerApp.signEIP712Message(this.#path, payload))
this.#emitter?.emit('getSignatureEnd')
this.#emitter?.emit('getSignatureStart')

// Normalize the signature for Ethers
obj.r = '0x' + obj.r
obj.s = '0x' + obj.s
let obj: { v: number; s: string; r: string }

return ethers.utils.joinSignature(obj)
try {
obj = await this.#retry(() => ledgerApp.signEIP712Message(this.#path, payload))
} catch {
const domainSeparatorHex = ethers.utils._TypedDataEncoder.hashDomain(payload.domain)
const hashStructMessageHex = ethers.utils._TypedDataEncoder.hashStruct(
payload.primaryType,
types,
payload.message
)
obj = await this.#retry(() =>
ledgerApp.signEIP712HashedMessage(this.#path, domainSeparatorHex, hashStructMessageHex)
)
}

this.#emitter?.emit('getSignatureEnd')

// Normalize the signature for Ethers
obj.r = '0x' + obj.r
obj.s = '0x' + obj.s

return ethers.utils.joinSignature(obj)
} catch (error) {
this.#emitter?.emit('getSignatureEnd')
throw error
}
}
}

Expand Down
107 changes: 107 additions & 0 deletions packages/bs-ethereum/src/__tests__/LedgerServiceEthereum.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
import { LedgerSigner } from '../LedgerServiceEthereum'
import { ethers } from 'ethers'

let ledgerSigner: LedgerSigner

describe('LedgerServiceEthereum', () => {
beforeAll(async () => {
const transport = await TransportNodeHid.create()
ledgerSigner = new LedgerSigner(transport)
}, 60000)

it('Should be able to get address', async () => {
const address = await ledgerSigner.getAddress()
expect(address).toBeDefined()
})

it('Should be able to get public key', async () => {
const publicKey = await ledgerSigner.getPublicKey()
expect(publicKey).toBeDefined()
})

it('Should be able to sign a message', async () => {
const message = 'Hello, World!'
const signedMessage = await ledgerSigner.signMessage(message)
expect(signedMessage).toBeDefined()
}, 60000)

it('Should be able to sign a transaction', async () => {
const transaction = {
from: '0xD833aBAa9fF467A1Ea1b999316F0b33117df08Fc',
to: '0xD833aBAa9fF467A1Ea1b999316F0b33117df08Fc',
data: '0x',
nonce: '0x09',
gasPrice: '0x05c21c',
gasLimit: '0x5208',
value: '0x00',
}

const signedTransaction = await ledgerSigner.signTransaction(transaction)

expect(signedTransaction).toBeDefined()
})

it.only('Should be able to sign a typed data', async () => {
const typedData = {
types: {
Person: [
{
name: 'name',
type: 'string',
},
{
name: 'wallet',
type: 'address',
},
],
Mail: [
{
name: 'from',
type: 'Person',
},
{
name: 'to',
type: 'Person',
},
{
name: 'contents',
type: 'string',
},
],
},
primaryType: 'Mail',
domain: {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
},
message: {
from: {
name: 'Cow',
wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
},
contents: 'Hello, Bob!',
oiiir: 'dsadsada',
},
}

const signature = await ledgerSigner._signTypedData(typedData.domain, typedData.types, typedData.message)

const signatureAddress = ethers.utils.verifyTypedData(
typedData.domain,
typedData.types,
typedData.message,
signature
)

const address = await ledgerSigner.getAddress()

expect(signatureAddress).toEqual(address)
}, 60000)
})
50 changes: 27 additions & 23 deletions packages/bs-neo3/src/LedgerServiceNeo3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,36 +36,40 @@ export class LedgerServiceNeo3 implements LedgerService {
}

async getSignature(transport: Transport, serializedTransaction: string, networkMagic: number, addressIndex = 0) {
this.emitter.emit('getSignatureStart')
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 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 chunks = serializedTransaction.match(/.{1,510}/g) || []
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])
}
for (let i = 0; i < chunks.length - 1; i++) {
await transport.send(0x80, 0x02, 2 + i, 0x80, Buffer.from(chunks[i], 'hex'), [0x9000])
}

const response = await transport.send(
0x80,
0x02,
2 + chunks.length,
0x00,
Buffer.from(chunks[chunks.length - 1], 'hex'),
[0x9000]
)

if (response.length <= 2) {
throw new Error(`No more data but Ledger did not return signature!`)
}
const response = await transport.send(
0x80,
0x02,
2 + chunks.length,
0x00,
Buffer.from(chunks[chunks.length - 1], 'hex'),
[0x9000]
)

const signature = this.derSignatureToHex(response.toString('hex'))
if (response.length <= 2) {
throw new Error(`No more data but Ledger did not return signature!`)
}

this.emitter.emit('getSignatureEnd')
const signature = this.derSignatureToHex(response.toString('hex'))

return signature
return signature
} finally {
this.emitter.emit('getSignatureEnd')
}
}

async getPublicKey(transport: Transport, addressIndex = 0): Promise<string> {
Expand Down

0 comments on commit b4ac988

Please sign in to comment.