/
cardano-ledger-crypto-provider.ts
148 lines (123 loc) · 4.19 KB
/
cardano-ledger-crypto-provider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import LedgerTransportU2F from '@ledgerhq/hw-transport-u2f'
import LedgerTransportWebusb from '@ledgerhq/hw-transport-webusb'
import Ledger from '@cardano-foundation/ledgerjs-hw-app-cardano'
import {encode} from 'borc'
import CachedDeriveXpubFactory from '../helpers/CachedDeriveXpubFactory'
import debugLog from '../../helpers/debugLog'
import {TxWitness, SignedTransactionStructured} from './byron-transaction'
import derivationSchemes from '../helpers/derivation-schemes'
import NamedError from '../../helpers/NamedError'
const CardanoLedgerCryptoProvider = async ({config}) => {
let transport
try {
transport = await LedgerTransportU2F.create()
} catch (u2fError) {
try {
transport = await LedgerTransportWebusb.create()
} catch (webUsbError) {
debugLog(webUsbError)
throw u2fError
}
}
transport.setExchangeTimeout(config.ADALITE_LOGOUT_AFTER * 1000)
const ledger = new Ledger(transport)
const derivationScheme = derivationSchemes.v2
const isHwWallet = () => true
const getHwWalletName = () => 'Ledger'
const {deriveXpub, cleanXpubCache} = CachedDeriveXpubFactory(
derivationScheme,
false,
async (absDerivationPath) => {
const response = await ledger.getExtendedPublicKeys(absDerivationPath)
const xpubHex = response.publicKeyHex + response.chainCodeHex
return Buffer.from(xpubHex, 'hex')
}
)
function deriveHdNode(childIndex) {
throw NamedError('UnsupportedOperationError', {
message: 'This operation is not supported on LedgerCryptoProvider!',
})
}
function sign(message, absDerivationPath) {
throw NamedError('UnsupportedOperationError', {message: 'Operation not supported'})
}
async function displayAddressForPath(absDerivationPath) {
try {
await ledger.showAddress(absDerivationPath)
} catch (err) {
throw NamedError('LedgerOperationError', {message: `${err.name}: ${err.message}`})
}
}
function getWalletSecret() {
throw NamedError('UnsupportedOperationError', {message: 'Unsupported operation!'})
}
function getDerivationScheme() {
return derivationScheme
}
function _prepareInput(input, addressToAbsPathMapper, txDataHex) {
return {
txDataHex,
outputIndex: input.outputIndex,
path: addressToAbsPathMapper(input.utxo.address),
}
}
interface LedgerOutput {
amountStr: string
address58?: string
path?: Array<number>
}
function _prepareOutput(output, addressToAbsPathMapper) {
const result: LedgerOutput = {
amountStr: `${output.coins}`,
}
if (output.isChange) {
result.path = addressToAbsPathMapper(output.address)
} else {
result.address58 = output.address
}
return result
}
async function prepareWitness(witness) {
const extendedPublicKey = await deriveXpub(witness.path)
return TxWitness(extendedPublicKey, Buffer.from(witness.witnessSignatureHex, 'hex'))
}
function prepareBody(unsignedTx, txWitnesses) {
return encode(SignedTransactionStructured(unsignedTx, txWitnesses)).toString('hex')
}
async function signTx(unsignedTx, rawInputTxs, addressToAbsPathMapper) {
const transactions = rawInputTxs.map((tx) => tx.toString('hex'))
const inputs = unsignedTx.inputs.map((input, i) =>
_prepareInput(input, addressToAbsPathMapper, transactions[i])
)
const outputs = unsignedTx.outputs.map((output) =>
_prepareOutput(output, addressToAbsPathMapper)
)
const response = await ledger.signTransaction(inputs, outputs)
if (response.txHashHex !== unsignedTx.getId()) {
throw NamedError('TxSerializationError', {
message: 'Tx serialization mismatch between Ledger and Adalite',
})
}
// serialize signed transaction for submission
const txWitnesses = await Promise.all(
response.witnesses.map((witness) => prepareWitness(witness))
)
return {
txHash: response.txHashHex,
txBody: prepareBody(unsignedTx, txWitnesses),
}
}
return {
getWalletSecret,
getDerivationScheme,
signTx,
displayAddressForPath,
deriveXpub,
isHwWallet,
getHwWalletName,
_sign: sign,
_deriveHdNode: deriveHdNode,
cleanXpubCache,
}
}
export default CardanoLedgerCryptoProvider