-
Notifications
You must be signed in to change notification settings - Fork 10
/
ots.js
216 lines (204 loc) · 6.86 KB
/
ots.js
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/* eslint new-cap: 0, max-depth: 0 */
const {Command, flags} = require('@oclif/command')
const {red, white} = require('kleur')
const ora = require('ora')
const validateQrlAddress = require('@theqrl/validate-qrl-address')
const grpc = require('grpc')
const {createClient} = require('grpc-kit')
const tmp = require('tmp')
const fs = require('fs')
const util = require('util')
const CryptoJS = require('crypto-js')
const aes256 = require('aes256')
const {cli} = require('cli-ux')
const {QRLPROTO_SHA256} = require('../get-qrl-proto-shasum')
const protoLoader = require('@grpc/proto-loader')
const PROTO_PATH = `${__dirname}/../../src/qrlbase.proto`
const readFile = util.promisify(fs.readFile)
const writeFile = util.promisify(fs.writeFile)
const clientGetNodeInfo = client => {
return new Promise((resolve, reject) => {
client.getNodeInfo({}, (error, response) => {
if (error) {
reject(error)
}
resolve(response)
})
})
}
const openWalletFile = function (path) {
const contents = fs.readFileSync(path)
return JSON.parse(contents)[0]
}
let qrlClient = null
async function checkProtoHash(file) {
return readFile(file).then(async contents => {
const protoFileWordArray = CryptoJS.lib.WordArray.create(contents)
const calculatedProtoHash = CryptoJS.SHA256(protoFileWordArray).toString(CryptoJS.enc.Hex)
let verified = false
QRLPROTO_SHA256.forEach(value => {
if (value.protoSha256 === calculatedProtoHash) {
verified = true
}
})
return verified
}).catch(error => {
throw new Error(error)
})
}
async function loadGrpcBaseProto(grpcEndpoint) {
return protoLoader.load(PROTO_PATH, {}).then(async packageDefinition => {
const packageObject = grpc.loadPackageDefinition(packageDefinition)
const client = await new packageObject.qrl.Base(grpcEndpoint, grpc.credentials.createInsecure())
const res = await clientGetNodeInfo(client)
const qrlProtoFilePath = tmp.fileSync({mode: '0644', prefix: 'qrl-', postfix: '.proto'}).name
await writeFile(qrlProtoFilePath, res.grpcProto).then(fsErr => {
if (fsErr) {
throw new Error('tmp filesystem error')
}
})
return qrlProtoFilePath
})
}
async function loadGrpcProto(protofile, endpoint) {
const options = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
}
const packageDefinition = await protoLoader.load(protofile, options)
const grpcObject = await grpc.loadPackageDefinition(packageDefinition)
const grpcObjectString = JSON.stringify(util.inspect(grpcObject.qrl, {showHidden: true, depth: 4}))
const protoObjectWordArray = CryptoJS.lib.WordArray.create(grpcObjectString)
const calculatedObjectHash = CryptoJS.SHA256(protoObjectWordArray).toString(CryptoJS.enc.Hex)
let verified = false
QRLPROTO_SHA256.forEach(value => {
if (value.objectSha256 === calculatedObjectHash) {
verified = true
}
})
// If the grpc object shasum matches, establish the grpc connection.
if (verified) {
qrlClient = createClient({
protoPath: protofile,
packageName: 'qrl',
serviceName: 'PublicAPI',
options: {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
},
}, endpoint)
} else {
throw new Error('Unable to verify proto file')
}
}
class OTSKey extends Command {
async run() {
const {args, flags} = this.parse(OTSKey)
let address = args.address
if (!validateQrlAddress.hexString(address).result) {
// not a valid address - is it a file?
let isFile = false
let isValidFile = false
const path = address
try {
if (fs.existsSync(path)) {
isFile = true
}
} catch (error) {
this.log(`${red('⨉')} Unable to get OTS: invalid QRL address/wallet file`)
this.exit(1)
}
if (isFile === false) {
this.log(`${red('⨉')} Unable to get OTS: invalid QRL address/wallet file`)
this.exit(1)
} else {
const walletJson = openWalletFile(path)
try {
if (walletJson.encrypted === false) {
isValidFile = true
address = walletJson.address
}
if (walletJson.encrypted === true) {
let password = ''
if (flags.password) {
password = flags.password
} else {
password = await cli.prompt('Enter password for wallet file', {type: 'hide'})
}
address = aes256.decrypt(password, walletJson.address)
if (validateQrlAddress.hexString(address).result) {
isValidFile = true
} else {
this.log(`${red('⨉')} Unable to open wallet file: invalid password`)
this.exit(1)
}
}
} catch (error) {
this.exit(1)
}
}
if (isValidFile === false) {
this.log(`${red('⨉')} Unable to get a balance: invalid QRL address/wallet file`)
this.exit(1)
}
}
let grpcEndpoint = 'testnet-4.automated.theqrl.org:19009'
let network = 'Testnet'
if (flags.grpc) {
grpcEndpoint = flags.grpc
network = `Custom GRPC endpoint: [${flags.grpc}]`
}
if (flags.testnet) {
grpcEndpoint = 'testnet-4.automated.theqrl.org:19009'
network = 'Testnet'
}
if (flags.mainnet) {
grpcEndpoint = 'mainnet-4.automated.theqrl.org:19009'
network = 'Mainnet'
}
this.log(white().bgBlue(network))
const spinner = ora({text: 'Fetching OTS from API...'}).start()
const proto = await loadGrpcBaseProto(grpcEndpoint)
checkProtoHash(proto).then(async protoHash => {
if (!protoHash) {
this.log(`${red('⨉')} Unable to validate .proto file from node`)
this.exit(1)
}
// next load GRPC object and check hash of that too
await loadGrpcProto(proto, grpcEndpoint)
const request = {
address: Buffer.from(address.substring(1), 'hex'),
}
await qrlClient.GetOTS(request, async (error, response) => {
if (error) {
this.log(`${red('⨉')} Unable to read next unused OTS key`)
}
spinner.succeed(`Next unused OTS key: ${response.next_unused_ots_index}`)
})
})
}
}
OTSKey.description = `Get a address's OTS state from the network
...
TODO
`
OTSKey.args = [
{
name: 'address',
description: 'address to return OTS state for',
required: true,
},
]
OTSKey.flags = {
testnet: flags.boolean({char: 't', default: false, description: 'queries testnet for the OTS state'}),
mainnet: flags.boolean({char: 'm', default: false, description: 'queries mainnet for the OTS state'}),
grpc: flags.string({char: 'g', required: false, description: 'advanced: grcp endpoint (for devnet/custom QRL network deployments)'}),
password: flags.string({char: 'p', required: false, description: 'wallet file password'}),
}
module.exports = {OTSKey}