lnbits-legend/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js

1007 lines
31 KiB
JavaScript
Raw Normal View History

2022-07-26 20:06:07 +03:00
async function serialSigner(path) {
const t = await loadTemplateAsync(path)
Vue.component('serial-signer', {
name: 'serial-signer',
template: t,
props: ['sats-denominated', 'network'],
2022-07-26 20:06:07 +03:00
data: function () {
return {
selectedPort: null,
writableStreamClosed: null,
writer: null,
readableStreamClosed: null,
reader: null,
receivedData: '',
config: {},
2022-08-11 08:59:06 +03:00
decryptionKey: null,
2022-09-22 10:41:54 +03:00
sharedSecret: null,
2022-07-27 12:26:07 +03:00
2022-07-26 20:06:07 +03:00
hww: {
password: null,
showPassword: false,
mnemonic: null,
showMnemonic: false,
quickMnemonicInput: false,
2022-08-22 09:04:16 +03:00
passphrase: null,
showPassphrase: false,
hasPassphrase: false,
2022-07-26 20:06:07 +03:00
authenticated: false,
showPasswordDialog: false,
2022-08-04 10:11:06 +03:00
showConfigDialog: false,
2022-07-26 20:06:07 +03:00
showWipeDialog: false,
showRestoreDialog: false,
2022-07-27 11:32:03 +03:00
showConfirmationDialog: false,
2022-07-26 20:06:07 +03:00
showSignedPsbt: false,
sendingPsbt: false,
signingPsbt: false,
loginResolve: null,
2022-08-01 18:53:26 +03:00
psbtSentResolve: null,
xpubResolve: null,
2022-08-02 16:42:06 +03:00
seedWordPosition: 1,
seedWord: null,
showSeedWord: false,
2022-08-02 16:42:06 +03:00
showSeedDialog: false,
2022-08-19 14:59:59 +03:00
// config: null,
2022-07-28 12:39:06 +03:00
confirm: {
outputIndex: 0,
showFee: false
2022-07-28 12:39:06 +03:00
}
2022-07-27 12:26:07 +03:00
},
2022-07-28 12:39:06 +03:00
tx: null, // todo: move to hww
2022-09-22 10:58:19 +03:00
showConsole: false,
showPairedDevices: true
2022-07-26 20:06:07 +03:00
}
},
2022-08-19 14:59:59 +03:00
computed: {
pairedDevices: {
2022-09-22 10:41:54 +03:00
cache: false,
2022-08-19 14:59:59 +03:00
get: function () {
return (
JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) ||
[]
)
},
set: function (devices) {
window.localStorage.setItem(
'lnbits-paired-devices',
JSON.stringify(devices)
)
}
}
},
2022-07-26 20:06:07 +03:00
methods: {
2022-07-28 12:39:06 +03:00
satBtc(val, showUnit = true) {
return satOrBtc(val, showUnit, this.satsDenominated)
},
2022-08-04 10:11:06 +03:00
openSerialPortDialog: async function () {
2022-08-19 14:59:59 +03:00
this.config = {...HWW_DEFAULT_CONFIG}
await this.openSerialPort(this.config)
2022-08-04 10:11:06 +03:00
},
openSerialPort: async function (config = {baudRate: 9600}) {
if (!this.checkSerialPortSupported()) return false
2022-08-04 10:11:06 +03:00
if (this.selectedPort) {
this.$q.notify({
type: 'warning',
message: 'Already connected. Disconnect first!',
timeout: 10000
})
return true
}
2022-07-26 20:06:07 +03:00
try {
2022-08-12 15:12:03 +03:00
this.selectedPort = await navigator.serial.requestPort()
this.selectedPort.addEventListener('connect', event => {
2022-08-19 14:59:59 +03:00
// do nothing
2022-07-26 20:06:07 +03:00
})
2022-08-12 15:12:03 +03:00
this.selectedPort.addEventListener('disconnect', () => {
this.selectedPort = null
2022-07-26 20:06:07 +03:00
this.hww.authenticated = false
this.$q.notify({
type: 'warning',
message: 'Disconnected from Serial Port!',
timeout: 10000
})
})
2022-08-12 15:12:03 +03:00
2022-07-26 20:06:07 +03:00
// Wait for the serial port to open.
2022-08-04 10:11:06 +03:00
await this.selectedPort.open(config)
2022-09-22 10:41:54 +03:00
// do not await
2022-07-26 20:06:07 +03:00
this.startSerialPortReading()
2022-09-22 10:41:54 +03:00
// wait to init
sleep(1000)
2022-07-26 20:06:07 +03:00
const textEncoder = new TextEncoderStream()
this.writableStreamClosed = textEncoder.readable.pipeTo(
this.selectedPort.writable
)
this.writer = textEncoder.writable.getWriter()
2022-08-18 08:38:40 +03:00
2022-08-19 18:05:05 +03:00
await this.hwwPing()
2022-08-18 08:38:40 +03:00
return true
2022-07-26 20:06:07 +03:00
} catch (error) {
this.selectedPort = null
this.$q.notify({
type: 'warning',
message: 'Cannot open serial port!',
caption: `${error}`,
timeout: 10000
})
return false
2022-07-26 20:06:07 +03:00
}
},
2022-08-19 14:59:59 +03:00
openSerialPortConfig: async function (deviceId) {
const device = this.getPairedDevice(deviceId)
if (device) {
this.config = device.config
} else {
this.config = {...HWW_DEFAULT_CONFIG}
}
2022-08-04 10:11:06 +03:00
this.hww.showConfigDialog = true
},
2022-07-27 09:16:36 +03:00
closeSerialPort: async function () {
try {
if (this.writer) this.writer.close()
if (this.writableStreamClosed) await this.writableStreamClosed
if (this.reader) this.reader.cancel()
if (this.readableStreamClosed)
await this.readableStreamClosed.catch(() => {
/* Ignore the error */
})
if (this.selectedPort) await this.selectedPort.close()
this.$q.notify({
type: 'positive',
message: 'Serial port disconnected!',
timeout: 5000
})
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Cannot close serial port!',
caption: `${error}`,
timeout: 10000
})
2022-07-27 09:27:51 +03:00
} finally {
this.selectedPort = null
2022-07-27 09:27:51 +03:00
this.hww.authenticated = false
2022-07-27 09:16:36 +03:00
}
},
isConnected: function () {
return !!this.selectedPort
},
isAuthenticated: function () {
return this.hww.authenticated
},
seedInputDone: function (mnemonic) {
this.hww.mnemonic = mnemonic
},
isAuthenticating: function () {
if (this.isAuthenticated()) return false
return new Promise(resolve => {
this.loginResolve = resolve
})
},
2022-07-27 11:32:03 +03:00
isSendingPsbt: async function () {
if (!this.hww.sendingPsbt) return false
return new Promise(resolve => {
this.psbtSentResolve = resolve
})
},
2022-08-01 18:53:26 +03:00
isFetchingXpub: async function () {
return new Promise(resolve => {
this.xpubResolve = resolve
})
},
2022-07-26 20:06:07 +03:00
checkSerialPortSupported: function () {
if (!navigator.serial) {
this.$q.notify({
type: 'warning',
message: 'Serial port communication not supported!',
caption:
'Make sure your browser supports Serial Port and that you are using HTTPS.',
timeout: 10000
})
return false
}
return true
},
startSerialPortReading: async function () {
const port = this.selectedPort
while (port && port.readable) {
const textDecoder = new TextDecoderStream()
this.readableStreamClosed = port.readable.pipeTo(textDecoder.writable)
this.reader = textDecoder.readable.getReader()
const readStringUntil = readFromSerialPort(this.reader)
try {
while (true) {
const {value, done} = await readStringUntil('\n')
if (value) {
2022-09-27 17:19:32 +03:00
const {command, commandData} = await this.extractCommand(value)
this.handleSerialPortResponse(command, commandData)
this.updateSerialPortConsole(command)
2022-07-26 20:06:07 +03:00
}
if (done) return
}
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Serial port communication error!',
caption: `${error}`,
timeout: 10000
})
}
}
},
2022-09-27 17:19:32 +03:00
handleSerialPortResponse: async function (command, commandData) {
2022-08-12 15:29:48 +03:00
this.logPublicCommandsResponse(command, commandData)
2022-08-12 10:25:54 +03:00
2022-08-01 18:53:26 +03:00
switch (command) {
2022-08-18 08:38:40 +03:00
case COMMAND_PING:
this.handlePingResponse(commandData)
break
2022-08-18 10:32:56 +03:00
case COMMAND_CHECK_PAIRING:
this.handleCheckPairingResponse(commandData)
break
2022-08-01 18:53:26 +03:00
case COMMAND_SIGN_PSBT:
this.handleSignResponse(commandData)
break
case COMMAND_PASSWORD:
this.handleLoginResponse(commandData)
break
case COMMAND_PASSWORD_CLEAR:
this.handleLogoutResponse(commandData)
break
case COMMAND_SEND_PSBT:
this.handleSendPsbtResponse(commandData)
break
case COMMAND_WIPE:
this.handleWipeResponse(commandData)
break
case COMMAND_XPUB:
this.handleXpubResponse(commandData)
break
2022-08-12 10:25:54 +03:00
case COMMAND_SEED:
this.handleShowSeedResponse(commandData)
2022-08-11 08:59:06 +03:00
break
2022-08-18 16:33:10 +03:00
case COMMAND_PAIR:
2022-08-18 17:54:26 +03:00
this.handlePairResponse(commandData)
2022-08-12 14:12:40 +03:00
break
case COMMAND_LOG:
console.log(
` %c${commandData}`,
'background: #222; color: #bada55'
)
break
2022-08-01 18:53:26 +03:00
default:
2022-09-27 17:19:32 +03:00
console.log(` %c${command}`, 'background: #222; color: red')
2022-08-01 18:53:26 +03:00
}
2022-07-26 20:06:07 +03:00
},
2022-08-12 16:52:55 +03:00
logPublicCommandsResponse: function (command, commandData) {
2022-08-12 15:29:48 +03:00
switch (command) {
case COMMAND_SIGN_PSBT:
case COMMAND_PASSWORD:
case COMMAND_PASSWORD_CLEAR:
case COMMAND_SEND_PSBT:
case COMMAND_WIPE:
case COMMAND_XPUB:
2022-08-18 16:33:10 +03:00
case COMMAND_PAIR:
2022-08-12 15:29:48 +03:00
console.log(
` %c${command} ${commandData}`,
'background: #222; color: yellow'
)
}
},
2022-07-26 20:06:07 +03:00
updateSerialPortConsole: function (value) {
this.receivedData += value + '\n'
2022-07-27 12:26:07 +03:00
const textArea = document.getElementById('serial-port-console')
2022-07-26 20:06:07 +03:00
if (textArea) textArea.scrollTop = textArea.scrollHeight
2022-08-18 08:38:40 +03:00
},
hwwPing: async function () {
try {
2022-09-22 10:41:54 +03:00
// Send an empty ping. The serial port buffer might have some jubk data. Flush it.
await this.sendCommandClearText(COMMAND_PING)
2022-08-18 08:38:40 +03:00
await this.sendCommandClearText(COMMAND_PING, [window.location.host])
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to ping Hardware Wallet!',
caption: `${error}`,
timeout: 10000
})
}
},
handlePingResponse: function (res = '') {
const [status, deviceId] = res.split(' ')
this.deviceId = deviceId
if (!this.deviceId) {
this.$q.notify({
type: 'warning',
message: 'Missing device ID for Hardware Wallet',
timeout: 10000
})
return
}
const device = this.getPairedDevice(deviceId)
if (device) {
2022-08-18 09:01:05 +03:00
this.sharedSecret = nobleSecp256k1.utils.hexToBytes(
device.sharedSecretHex
)
2022-08-18 10:32:56 +03:00
this.hwwCheckPairing()
2022-08-18 08:38:40 +03:00
} else {
2022-08-18 17:54:26 +03:00
this.hwwPair()
2022-08-18 08:38:40 +03:00
}
2022-07-26 20:06:07 +03:00
},
hwwShowPasswordDialog: async function () {
try {
this.hww.showPasswordDialog = true
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_PASSWORD)
2022-07-26 20:06:07 +03:00
} catch (error) {
2022-08-19 17:49:01 +03:00
console.log(error)
2022-07-26 20:06:07 +03:00
this.$q.notify({
type: 'warning',
message: 'Failed to connect to Hardware Wallet!',
caption: `${error}`,
timeout: 10000
})
}
},
hwwShowWipeDialog: async function () {
try {
this.hww.showWipeDialog = true
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_WIPE)
2022-07-26 20:06:07 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to connect to Hardware Wallet!',
caption: `${error}`,
timeout: 10000
})
}
},
hwwShowRestoreDialog: async function () {
try {
this.hww.showRestoreDialog = true
2022-08-15 15:43:35 +03:00
await this.sendCommandSecure(COMMAND_RESTORE)
2022-07-26 20:06:07 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to connect to Hardware Wallet!',
caption: `${error}`,
timeout: 10000
})
}
},
closeSeedDialog: function () {
this.hww.seedWord = null
this.hww.showSeedWord = false
},
2022-07-28 12:39:06 +03:00
hwwConfirmNext: async function () {
this.hww.confirm.outputIndex += 1
if (this.hww.confirm.outputIndex >= this.tx.outputs.length) {
this.hww.confirm.showFee = true
}
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_CONFIRM_NEXT)
2022-07-28 12:39:06 +03:00
},
2022-07-28 15:02:17 +03:00
cancelOperation: async function () {
try {
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_CANCEL)
2022-07-28 15:02:17 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to send cancel!',
caption: `${error}`,
timeout: 10000
})
}
},
2022-08-04 10:11:06 +03:00
hwwConfigAndConnect: async function () {
this.hww.showConfigDialog = false
2022-08-19 14:59:59 +03:00
if (this.config.deviceId) {
this.updatePairedDeviceConfig(this.config.deviceId, this.config)
}
await this.openSerialPort(this.config)
2022-08-04 10:11:06 +03:00
return true
},
2022-07-26 20:06:07 +03:00
hwwLogin: async function () {
try {
2022-09-08 16:37:02 +03:00
await this.sendCommandSecure(COMMAND_PASSWORD, [
this.hww.password,
this.hww.passphrase
])
2022-07-26 20:06:07 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to send password to Hardware Wallet!',
caption: `${error}`,
timeout: 10000
})
} finally {
this.hww.showPasswordDialog = false
this.hww.password = null
2022-09-08 16:37:02 +03:00
this.hww.passphrase = null
2022-07-26 20:06:07 +03:00
this.hww.showPassword = false
2022-09-08 16:37:02 +03:00
this.hww.showPassphrase = false
2022-07-26 20:06:07 +03:00
}
},
handleLoginResponse: function (res = '') {
this.hww.authenticated = res.trim() === '1'
2022-07-29 18:10:13 +03:00
if (this.loginResolve) {
this.loginResolve(this.hww.authenticated)
}
2022-07-26 20:06:07 +03:00
if (this.hww.authenticated) {
this.$q.notify({
type: 'positive',
message: 'Login successfull!',
timeout: 10000
})
} else {
this.$q.notify({
type: 'warning',
message: 'Wrong password, try again!',
timeout: 10000
})
}
},
hwwLogout: async function () {
try {
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_PASSWORD_CLEAR)
2022-07-26 20:06:07 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to logout from Hardware Wallet!',
caption: `${error}`,
timeout: 10000
})
}
},
2022-09-08 16:37:02 +03:00
hwwShowAddress: async function (path, address) {
try {
await this.sendCommandSecure(COMMAND_ADDRESS, [
this.network,
path,
address
])
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to logout from Hardware Wallet!',
caption: `${error}`,
timeout: 10000
})
}
},
2022-07-26 20:06:07 +03:00
handleLogoutResponse: function (res = '') {
2022-08-18 18:57:20 +03:00
const authenticated = !(res.trim() === '1')
if (this.hww.authenticated && !authenticated) {
2022-07-26 20:06:07 +03:00
this.$q.notify({
2022-08-18 18:57:20 +03:00
type: 'positive',
message: 'Logged Out',
2022-07-26 20:06:07 +03:00
timeout: 10000
})
}
2022-08-18 18:57:20 +03:00
this.hww.authenticated = authenticated
2022-07-26 20:06:07 +03:00
},
2022-07-28 12:39:06 +03:00
hwwSendPsbt: async function (psbtBase64, tx) {
2022-07-26 20:06:07 +03:00
try {
2022-07-28 12:39:06 +03:00
this.tx = tx
2022-07-26 20:06:07 +03:00
this.hww.sendingPsbt = true
2022-08-12 10:25:54 +03:00
await this.sendCommandSecure(COMMAND_SEND_PSBT, [
this.network,
psbtBase64
])
2022-07-26 20:06:07 +03:00
this.$q.notify({
type: 'positive',
message: 'Data sent to serial port device!',
timeout: 5000
})
} catch (error) {
2022-07-27 11:32:03 +03:00
this.hww.sendingPsbt = false
2022-07-26 20:06:07 +03:00
this.$q.notify({
type: 'warning',
message: 'Failed to send data to serial port!',
caption: `${error}`,
timeout: 10000
})
}
},
handleSendPsbtResponse: function (res = '') {
2022-08-01 15:53:01 +03:00
try {
const psbtOK = res.trim() === '1'
if (!psbtOK) {
this.$q.notify({
type: 'warning',
message: 'Failed to send PSBT!',
caption: `${res}`,
timeout: 10000
})
return
}
this.hww.confirm.outputIndex = 0
this.hww.showConfirmationDialog = true
this.hww.confirm = {
outputIndex: 0,
showFee: false
}
this.hww.sendingPsbt = false
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to send PSBT!',
caption: `${error}`,
timeout: 10000
})
} finally {
this.psbtSentResolve()
2022-07-28 12:39:06 +03:00
}
2022-07-26 20:06:07 +03:00
},
hwwSignPsbt: async function () {
try {
2022-07-28 12:39:06 +03:00
this.hww.showConfirmationDialog = false
2022-07-26 20:06:07 +03:00
this.hww.signingPsbt = true
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_SIGN_PSBT)
2022-07-26 20:06:07 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to sign PSBT!',
caption: `${error}`,
timeout: 10000
})
}
},
handleSignResponse: function (res = '') {
this.hww.signingPsbt = false
2022-08-04 14:04:49 +03:00
const [count, psbt] = res.trim().split(' ')
if (!psbt || !count || count.trim() === '0') {
this.$q.notify({
type: 'warning',
message: 'No input signed!',
caption: 'Are you using the right seed?',
timeout: 10000
})
return
}
this.updateSignedPsbt(psbt)
2022-08-02 16:42:06 +03:00
this.$q.notify({
type: 'positive',
message: 'Transaction Signed',
2022-08-04 14:04:49 +03:00
message: `Inputs signed: ${count}`,
2022-08-02 16:42:06 +03:00
timeout: 10000
})
2022-07-26 20:06:07 +03:00
},
2022-08-18 10:32:56 +03:00
hwwCheckPairing: async function () {
2022-08-18 08:38:40 +03:00
const iv = window.crypto.getRandomValues(new Uint8Array(16))
const encrypted = await this.encryptMessage(
2022-09-22 10:41:54 +03:00
this.sharedSecret, // todo: revisit
2022-08-18 08:38:40 +03:00
iv,
2022-08-18 18:57:20 +03:00
PAIRING_CONTROL_TEXT.length + ' ' + PAIRING_CONTROL_TEXT
2022-08-18 08:38:40 +03:00
)
const encryptedHex = nobleSecp256k1.utils.bytesToHex(encrypted)
const encryptedIvHex = nobleSecp256k1.utils.bytesToHex(iv)
try {
2022-08-18 09:01:05 +03:00
await this.sendCommandClearText(COMMAND_CHECK_PAIRING, [
encryptedHex + encryptedIvHex
])
2022-08-18 08:38:40 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to check secure connection!',
caption: `${error}`,
timeout: 10000
})
}
},
2022-08-18 10:32:56 +03:00
handleCheckPairingResponse: async function (res = '') {
const [statusCode, message] = res.split(' ')
2022-08-18 18:57:20 +03:00
switch (statusCode) {
case '0':
const controlText = await this.decryptData(message)
2022-08-18 18:57:20 +03:00
if (controlText == PAIRING_CONTROL_TEXT) {
this.$q.notify({
type: 'positive',
message: 'Re-paired with success!',
timeout: 10000
})
} else {
this.$q.notify({
type: 'warning',
message: 'Re-pairing failed!',
caption: 'Remove (forget) device and try again!',
timeout: 10000
})
}
break
case '1':
this.closeSerialPort()
this.$q.notify({
type: 'warning',
message:
'Re-pairing failed. Remove (forget) device and try again!',
caption: `Error: ${message}`,
timeout: 10000
})
break
2022-08-18 18:57:20 +03:00
default:
// noting to do here yet
break
}
2022-08-18 08:38:40 +03:00
},
2022-08-18 17:54:26 +03:00
hwwPair: async function () {
2022-07-26 20:06:07 +03:00
try {
2022-08-11 08:59:06 +03:00
this.decryptionKey = nobleSecp256k1.utils.randomPrivateKey()
const publicKey = nobleSecp256k1.Point.fromPrivateKey(
this.decryptionKey
)
const publicKeyHex = publicKey.toHex().slice(2)
2022-08-18 16:33:10 +03:00
2022-08-19 17:49:01 +03:00
const args = [publicKeyHex]
if (Number.isInteger(+this.config.buttonOnePin)) {
args.push(this.config.buttonOnePin)
}
if (Number.isInteger(+this.config.buttonTwoPin)) {
args.push(this.config.buttonTwoPin)
}
await this.sendCommandClearText(COMMAND_PAIR, args)
2022-08-12 15:12:03 +03:00
this.$q.notify({
type: 'positive',
2022-08-18 17:54:26 +03:00
message: 'Pairing started!',
2022-08-12 15:12:03 +03:00
timeout: 5000
})
} catch (error) {
this.$q.notify({
type: 'warning',
2022-08-18 17:54:26 +03:00
message: 'Failed to pair with device!',
2022-08-12 15:12:03 +03:00
caption: `${error}`,
timeout: 10000
})
}
},
2022-08-18 17:54:26 +03:00
handlePairResponse: async function (res = '') {
2022-08-18 08:38:40 +03:00
const [statusCode, data] = res.trim().split(' ')
let pubKeyHex, errorMessage, captionMessage
switch (statusCode) {
case '0':
pubKeyHex = data
if (!data) errorMessage = 'Failed to exchange DH secret!'
break
case '1':
errorMessage =
2022-08-18 17:54:26 +03:00
'Device pairing only possible in the first 10 seconds after start-up!'
2022-08-18 08:38:40 +03:00
captionMessage = 'Restart and try again'
break
default:
errorMessage = 'Unexpected error code'
break
}
if (errorMessage) {
this.$q.notify({
type: 'warning',
message: errorMessage,
caption: captionMessage || '',
timeout: 10000
})
2022-08-18 17:54:26 +03:00
this.closeSerialPort()
2022-08-18 08:38:40 +03:00
return
}
const hwwPublicKey = nobleSecp256k1.Point.fromHex('04' + pubKeyHex)
this.sharedSecret = nobleSecp256k1
.getSharedSecret(this.decryptionKey, hwwPublicKey)
.slice(1, 33)
2022-08-19 08:51:57 +03:00
const sharedSecretHex = nobleSecp256k1.utils.bytesToHex(
this.sharedSecret
)
const sharedSecredHash = await nobleSecp256k1.utils.sha256(
asciiToUint8Array(sharedSecretHex)
)
const fingerprint = nobleSecp256k1.utils
.bytesToHex(sharedSecredHash)
.substring(0, 5)
.toUpperCase()
2022-08-18 17:54:26 +03:00
LNbits.utils
2022-08-19 08:51:57 +03:00
.confirmDialog('Confirm code from display: ' + fingerprint)
.onOk(() => {
this.addPairedDevice(
this.deviceId,
2022-08-19 14:59:59 +03:00
nobleSecp256k1.utils.bytesToHex(this.sharedSecret),
this.config
2022-08-19 08:51:57 +03:00
)
2022-08-18 17:54:26 +03:00
2022-08-19 08:51:57 +03:00
this.$q.notify({
type: 'positive',
message: 'Paired with device!',
timeout: 5000
})
})
.onCancel(() => {
this.closeSerialPort()
})
2022-08-18 08:38:40 +03:00
},
2022-08-12 15:12:03 +03:00
hwwHelp: async function () {
try {
2022-08-12 15:55:19 +03:00
await this.sendCommandSecure(COMMAND_HELP)
2022-07-26 20:06:07 +03:00
this.$q.notify({
type: 'positive',
message: 'Check display or console for details!',
timeout: 5000
})
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to ask for help!',
caption: `${error}`,
timeout: 10000
})
}
},
hwwWipe: async function () {
try {
this.hww.showWipeDialog = false
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_WIPE, [this.hww.password])
2022-07-26 20:06:07 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
2022-09-22 10:41:54 +03:00
message: 'Failed to wipe!',
2022-07-26 20:06:07 +03:00
caption: `${error}`,
timeout: 10000
})
} finally {
this.hww.password = null
this.hww.confirmedPassword = null
2022-07-26 20:06:07 +03:00
this.hww.showPassword = false
}
},
handleWipeResponse: function (res = '') {
const wiped = res.trim() === '1'
if (wiped) {
this.$q.notify({
type: 'positive',
message: 'Wallet wiped!',
timeout: 10000
})
} else {
this.$q.notify({
type: 'warning',
message: 'Failed to wipe wallet!',
caption: `${error}`,
timeout: 10000
})
}
},
2022-08-01 18:53:26 +03:00
hwwXpub: async function (path) {
try {
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_XPUB, [this.network, path])
2022-08-01 18:53:26 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to fetch XPub!',
caption: `${error}`,
timeout: 10000
})
}
},
handleXpubResponse: function (res = '') {
const args = res.trim().split(' ')
if (args.length < 3 || args[0].trim() !== '1') {
this.$q.notify({
type: 'warning',
message: 'Failed to fetch XPub!',
caption: `${res}`,
timeout: 10000
})
this.xpubResolve({})
return
}
const xpub = args[1].trim()
const fingerprint = args[2].trim()
this.xpubResolve({xpub, fingerprint})
},
2022-08-18 09:01:05 +03:00
2022-07-26 20:06:07 +03:00
hwwShowSeed: async function () {
try {
2022-08-02 16:42:06 +03:00
this.hww.showSeedDialog = true
this.hww.seedWordPosition = 1
2022-08-11 08:59:06 +03:00
2022-08-12 10:25:54 +03:00
await this.sendCommandSecure(COMMAND_SEED, [
this.hww.seedWordPosition
])
2022-07-26 20:06:07 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to show seed!',
caption: `${error}`,
timeout: 10000
})
}
},
2022-08-02 16:42:06 +03:00
showNextSeedWord: async function () {
this.hww.seedWordPosition++
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_SEED, [this.hww.seedWordPosition])
2022-08-02 16:42:06 +03:00
},
showPrevSeedWord: async function () {
this.hww.seedWordPosition = Math.max(1, this.hww.seedWordPosition - 1)
2022-08-11 08:59:06 +03:00
await this.sendCommandSecure(COMMAND_SEED, [this.hww.seedWordPosition])
2022-08-02 16:42:06 +03:00
},
handleShowSeedResponse: function (res = '') {
const [pos, word] = res.trim().split(' ')
this.hww.seedWord = `${pos}. ${word}`
this.hww.seedWordPosition = pos
2022-08-02 16:42:06 +03:00
},
2022-07-26 20:06:07 +03:00
hwwRestore: async function () {
try {
await this.sendCommandSecure(COMMAND_RESTORE, [
this.hww.password,
2022-09-08 16:37:02 +03:00
this.hww.mnemonic
])
2022-07-26 20:06:07 +03:00
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to restore from seed!',
caption: `${error}`,
timeout: 10000
})
} finally {
this.hww.showRestoreDialog = false
this.hww.mnemonic = null
this.hww.showMnemonic = false
this.hww.password = null
this.hww.confirmedPassword = null
2022-07-26 20:06:07 +03:00
this.hww.showPassword = false
}
2022-07-27 11:32:03 +03:00
},
2022-08-02 16:42:06 +03:00
2022-07-27 11:32:03 +03:00
updateSignedPsbt: async function (value) {
this.$emit('signed:psbt', value)
2022-08-11 08:59:06 +03:00
},
sendCommandSecure: async function (command, attrs = []) {
const message = [command].concat(attrs).join(' ')
2022-08-12 16:52:55 +03:00
const iv = window.crypto.getRandomValues(new Uint8Array(16))
2022-09-22 10:41:54 +03:00
if (!this.sharedSecret || !this.sharedSecret.length) {
throw new Error(
`Secure connection not estabileshed. Tried to run command: ${command}`
)
}
2022-08-12 16:52:55 +03:00
const encrypted = await this.encryptMessage(
2022-08-12 15:12:03 +03:00
this.sharedSecret,
2022-08-12 16:52:55 +03:00
iv,
2022-08-12 15:12:03 +03:00
message.length + ' ' + message
)
2022-08-11 08:59:06 +03:00
const encryptedHex = nobleSecp256k1.utils.bytesToHex(encrypted)
2022-08-12 16:52:55 +03:00
const encryptedIvHex = nobleSecp256k1.utils.bytesToHex(iv)
await this.writer.write(encryptedHex + encryptedIvHex + '\n')
2022-08-12 14:12:40 +03:00
},
2022-08-18 08:38:40 +03:00
sendCommandClearText: async function (command, attrs = []) {
const message = [command].concat(attrs).join(' ')
await this.writer.write(message + '\n')
},
2022-08-12 14:12:40 +03:00
extractCommand: async function (value) {
const command = value.split(' ')[0]
const commandData = value.substring(command.length).trim()
2022-08-12 10:25:54 +03:00
if (
2022-08-18 16:33:10 +03:00
command === COMMAND_PAIR ||
command === COMMAND_LOG ||
2022-08-18 08:38:40 +03:00
command === COMMAND_PASSWORD_CLEAR ||
2022-08-18 10:32:56 +03:00
command === COMMAND_PING ||
command === COMMAND_CHECK_PAIRING
)
2022-08-12 14:12:40 +03:00
return {command, commandData}
2022-08-12 10:25:54 +03:00
2022-08-12 14:12:40 +03:00
const decryptedValue = await this.decryptData(value)
const decryptedCommand = decryptedValue.split(' ')[0]
2022-08-12 15:12:03 +03:00
const decryptedCommandData = decryptedValue
.substring(decryptedCommand.length)
.trim()
2022-08-12 14:12:40 +03:00
return {
command: decryptedCommand,
commandData: decryptedCommandData
}
},
decryptData: async function (value) {
if (!this.sharedSecret) {
return '/error Secure session not established!'
}
try {
2022-08-12 16:52:55 +03:00
const ivSize = 32
const messageHex = value.substring(0, value.length - ivSize)
const ivHex = value.substring(value.length - ivSize)
const messageBytes = nobleSecp256k1.utils.hexToBytes(messageHex)
const iv = nobleSecp256k1.utils.hexToBytes(ivHex)
const decrypted1 = await this.decryptMessage(
2022-08-12 14:12:40 +03:00
this.sharedSecret,
2022-08-12 16:52:55 +03:00
iv,
2022-08-12 14:12:40 +03:00
messageBytes
)
const data = new TextDecoder().decode(decrypted1)
const [len] = data.split(' ')
2022-08-12 15:12:03 +03:00
const command = data
.substring(len.length + 1, +len + len.length + 1)
.trim()
2022-08-12 14:12:40 +03:00
return command
} catch (error) {
return '/error Failed to decrypt message from device!'
}
2022-08-12 16:52:55 +03:00
},
encryptMessage: async function (key, iv, message) {
while (message.length % 16 !== 0) message += ' '
const encodedMessage = asciiToUint8Array(message)
2022-08-12 16:52:55 +03:00
const aesCbc = new aesjs.ModeOfOperation.cbc(key, iv)
const encryptedBytes = aesCbc.encrypt(encodedMessage)
2022-08-12 16:52:55 +03:00
return encryptedBytes
},
decryptMessage: async function (key, iv, encryptedBytes) {
const aesCbc = new aesjs.ModeOfOperation.cbc(key, iv)
const decryptedBytes = aesCbc.decrypt(encryptedBytes)
return decryptedBytes
2022-08-18 08:38:40 +03:00
},
2022-08-19 14:59:59 +03:00
2022-08-18 09:01:05 +03:00
getPairedDevice: function (deviceId) {
2022-08-19 14:59:59 +03:00
return this.pairedDevices.find(d => d.id === deviceId)
2022-08-18 08:38:40 +03:00
},
2022-08-18 09:01:05 +03:00
removePairedDevice: function (deviceId) {
2022-08-19 14:59:59 +03:00
const devices = this.pairedDevices
const deviceIndex = devices.findIndex(d => d.id === deviceId)
2022-08-18 08:38:40 +03:00
if (deviceIndex !== -1) {
devices.splice(deviceIndex, 1)
}
2022-08-19 14:59:59 +03:00
this.pairedDevices = devices
2022-09-22 10:58:19 +03:00
this.showPairedDevices = false
setTimeout(() => {
// force UI refresh
this.showPairedDevices = true
})
2022-08-18 08:38:40 +03:00
},
2022-08-19 14:59:59 +03:00
addPairedDevice: function (deviceId, sharedSecretHex, config) {
const devices = this.pairedDevices
config.deviceId = deviceId
devices.unshift({
2022-08-18 08:38:40 +03:00
id: deviceId,
sharedSecretHex: sharedSecretHex,
2022-08-19 14:59:59 +03:00
pairingDate: new Date().toISOString(),
config
2022-08-18 08:38:40 +03:00
})
2022-08-19 14:59:59 +03:00
this.pairedDevices = devices
2022-09-22 10:58:19 +03:00
this.showPairedDevices = false
setTimeout(() => {
// force UI refresh
this.showPairedDevices = true
})
2022-08-19 14:59:59 +03:00
},
updatePairedDeviceConfig(deviceId, config) {
const device = this.getPairedDevice(deviceId)
if (device) {
this.removePairedDevice(deviceId)
this.addPairedDevice(deviceId, device.sharedSecretHex, config)
}
2022-07-26 20:06:07 +03:00
}
},
2022-08-18 09:01:05 +03:00
created: async function () {}
2022-07-26 20:06:07 +03:00
})
}