@leocuvee/turtlecoin-utils 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +27 -0
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/inspectionProfiles/Project_Default.xml +7 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/turtlecoin-utils.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.travis.yml +11 -0
- package/CONTRIBUTING.md +3 -0
- package/LICENSE +674 -0
- package/README.md +203 -0
- package/config.json +7 -0
- package/docs/.nojekyll +0 -0
- package/docs/CNAME +1 -0
- package/docs/assets/css/main.css +2321 -0
- package/docs/assets/images/icons.png +0 -0
- package/docs/assets/images/icons@2x.png +0 -0
- package/docs/assets/images/widgets.png +0 -0
- package/docs/assets/images/widgets@2x.png +0 -0
- package/docs/assets/js/main.js +1 -0
- package/docs/assets/js/search.js +3 -0
- package/docs/classes/address.html +964 -0
- package/docs/classes/addressprefix.html +431 -0
- package/docs/classes/block.html +965 -0
- package/docs/classes/blocktemplate.html +695 -0
- package/docs/classes/cryptonote.html +1137 -0
- package/docs/classes/ed25519.keypair.html +400 -0
- package/docs/classes/ed25519.keys.html +373 -0
- package/docs/classes/extranoncetag.extranoncedata.html +454 -0
- package/docs/classes/extranoncetag.extranoncepaymentid.html +453 -0
- package/docs/classes/extranoncetag.iextranonce.html +347 -0
- package/docs/classes/extratag.extramergedmining.html +494 -0
- package/docs/classes/extratag.extranonce.html +530 -0
- package/docs/classes/extratag.extrapadding.html +456 -0
- package/docs/classes/extratag.extrapublickey.html +460 -0
- package/docs/classes/extratag.iextratag.html +355 -0
- package/docs/classes/levinpacket.html +674 -0
- package/docs/classes/levinpayloads.handshake.html +731 -0
- package/docs/classes/levinpayloads.ilevinpayload.html +318 -0
- package/docs/classes/levinpayloads.liteblock.html +494 -0
- package/docs/classes/levinpayloads.missingtransactions.html +494 -0
- package/docs/classes/levinpayloads.newblock.html +540 -0
- package/docs/classes/levinpayloads.newtransactions.html +402 -0
- package/docs/classes/levinpayloads.peerentry.html +610 -0
- package/docs/classes/levinpayloads.ping.html +450 -0
- package/docs/classes/levinpayloads.rawblock.html +344 -0
- package/docs/classes/levinpayloads.requestchain.html +402 -0
- package/docs/classes/levinpayloads.requestgetobjects.html +448 -0
- package/docs/classes/levinpayloads.requesttxpool.html +402 -0
- package/docs/classes/levinpayloads.responsechain.html +494 -0
- package/docs/classes/levinpayloads.responsegetobjects.html +540 -0
- package/docs/classes/levinpayloads.timedsync.html +540 -0
- package/docs/classes/multisig.html +930 -0
- package/docs/classes/multisigmessage.html +694 -0
- package/docs/classes/parentblock.html +347 -0
- package/docs/classes/transaction.html +925 -0
- package/docs/classes/transactioninputs.coinbaseinput.html +390 -0
- package/docs/classes/transactioninputs.itransactioninput.html +321 -0
- package/docs/classes/transactioninputs.keyinput.html +459 -0
- package/docs/classes/transactionoutputs.itransactionoutput.html +317 -0
- package/docs/classes/transactionoutputs.keyoutput.html +422 -0
- package/docs/enums/extranoncetag.noncetagtype.html +246 -0
- package/docs/enums/extratag.extratagtype.html +280 -0
- package/docs/enums/levinprotocol.commandtype.html +391 -0
- package/docs/enums/transactioninputs.inputtype.html +246 -0
- package/docs/enums/transactionoutputs.outputtype.html +229 -0
- package/docs/globals.html +238 -0
- package/docs/index.html +271 -0
- package/docs/interfaces/interfaces.config.html +590 -0
- package/docs/interfaces/interfaces.daemonblocktemplateresponse.html +323 -0
- package/docs/interfaces/interfaces.generatedinput.html +304 -0
- package/docs/interfaces/interfaces.generatedoutput.html +285 -0
- package/docs/interfaces/interfaces.inputkeys.html +304 -0
- package/docs/interfaces/interfaces.ipreparedtransaction.html +268 -0
- package/docs/interfaces/interfaces.output.html +399 -0
- package/docs/interfaces/interfaces.preparedringsignature.html +377 -0
- package/docs/interfaces/interfaces.preparedtransaction.html +329 -0
- package/docs/interfaces/interfaces.randomoutput.html +285 -0
- package/docs/interfaces/interfaces.transactionrecipient.html +285 -0
- package/docs/interfaces/multisiginterfaces.partialkeyimage.html +277 -0
- package/docs/interfaces/multisiginterfaces.partialsigningkey.html +277 -0
- package/docs/modules/ed25519.html +195 -0
- package/docs/modules/extranoncetag.html +208 -0
- package/docs/modules/extratag.html +216 -0
- package/docs/modules/interfaces.html +231 -0
- package/docs/modules/levinpayloads.html +247 -0
- package/docs/modules/levinprotocol.html +191 -0
- package/docs/modules/multisiginterfaces.html +195 -0
- package/docs/modules/transactioninputs.html +208 -0
- package/docs/modules/transactionoutputs.html +204 -0
- package/index.d.ts +417 -0
- package/index.js +1508 -0
- package/lib/base58.js +220 -0
- package/lib/biginteger.js +1591 -0
- package/lib/blocktemplate.js +408 -0
- package/lib/crypto.js +19698 -0
- package/lib/mnemonic.js +1204 -0
- package/lib/nacl-fast-cn.js +608 -0
- package/lib/ringsigs.js +24262 -0
- package/lib/sha3.js +477 -0
- package/package.json +58 -0
- package/src/Address.ts +433 -0
- package/src/AddressPrefix.ts +117 -0
- package/src/Block.ts +556 -0
- package/src/BlockTemplate.ts +289 -0
- package/src/Common.ts +105 -0
- package/src/Config.ts +66 -0
- package/src/CryptoNote.ts +1072 -0
- package/src/LevinPacket.ts +366 -0
- package/src/Multisig.ts +600 -0
- package/src/MultisigMessage.ts +374 -0
- package/src/ParentBlock.ts +39 -0
- package/src/Transaction.ts +628 -0
- package/src/Types/ED25519.ts +187 -0
- package/src/Types/IExtraNonce.ts +225 -0
- package/src/Types/IExtraTag.ts +507 -0
- package/src/Types/ITransaction.ts +230 -0
- package/src/Types/ITransactionInput.ts +190 -0
- package/src/Types/ITransactionOutput.ts +108 -0
- package/src/Types/LevinPayloads.ts +1576 -0
- package/src/Types/MultisigInterfaces.ts +65 -0
- package/src/Types/PortableStorage.ts +289 -0
- package/src/Types.ts +36 -0
- package/src/index.ts +36 -0
- package/test/template.json +6 -0
- package/test/test.js +1457 -0
- package/tests/blocktemplate.json +6 -0
- package/tests/tests.js +215 -0
- package/tsconfig.json +15 -0
- package/tslint.json +36 -0
- package/typedoc.json +10 -0
- package/webpack.config.js +15 -0
package/src/Multisig.ts
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
// Copyright (c) 2020, The TurtleCoin Developers
|
|
2
|
+
//
|
|
3
|
+
// Please see the included LICENSE file for more information.
|
|
4
|
+
|
|
5
|
+
import {Address} from './Address';
|
|
6
|
+
import {ED25519} from './Types/ED25519';
|
|
7
|
+
import {Interfaces, MultisigInterfaces, TransactionInputs, TurtleCoinCrypto} from './Types';
|
|
8
|
+
import {Transaction} from './Transaction';
|
|
9
|
+
/** @ignore */
|
|
10
|
+
import KeyPair = ED25519.KeyPair;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents a multisig helper class that can be used for the creation of multisig wallets
|
|
14
|
+
*/
|
|
15
|
+
export class Multisig {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns an address object representing the multisig wallet address
|
|
19
|
+
*/
|
|
20
|
+
public get address(): Address {
|
|
21
|
+
if (!this.isReady) {
|
|
22
|
+
throw new Error('Not all participants have been loaded');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return Address.fromViewOnlyKeys(this.spend, this.view);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns the threshold (M) of the multisig wallet
|
|
30
|
+
*/
|
|
31
|
+
public get threshold(): number {
|
|
32
|
+
return this.m_threshold;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns the participants (N) of the multisig wallet
|
|
37
|
+
*/
|
|
38
|
+
public get participants(): number {
|
|
39
|
+
return this.m_participants;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns the number of participants currently loaded into the object
|
|
44
|
+
*/
|
|
45
|
+
public get current_participants(): number {
|
|
46
|
+
return this.m_currentParticipants;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Returns the shared private view key of the multisig wallet
|
|
51
|
+
*/
|
|
52
|
+
public get view(): string {
|
|
53
|
+
if (!this.isViewReady) {
|
|
54
|
+
throw new Error('Not all participants have been loaded');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return TurtleCoinCrypto.calculateSharedPrivateKey(this.m_view_keys);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns the shared public spend key of the multisig wallet
|
|
62
|
+
*/
|
|
63
|
+
public get spend(): string {
|
|
64
|
+
if (!this.isSpendReady) {
|
|
65
|
+
throw new Error('Not all participants have been loaded');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const keys: string[] = this.m_participant_keys;
|
|
69
|
+
|
|
70
|
+
this.m_multisig_keys.forEach((key) => {
|
|
71
|
+
if (key.publicKey.length !== 0 &&
|
|
72
|
+
keys.indexOf(key.publicKey) === -1
|
|
73
|
+
) {
|
|
74
|
+
keys.push(key.publicKey);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return TurtleCoinCrypto.calculateSharedPublicKey(keys);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns if the object is ready for export and/or use
|
|
83
|
+
*/
|
|
84
|
+
public get isReady(): boolean {
|
|
85
|
+
return (this.isViewReady && this.isSpendReady);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns our multisig keys
|
|
90
|
+
*/
|
|
91
|
+
public get multisig_keys(): KeyPair[] {
|
|
92
|
+
return this.m_multisig_keys;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns the public multisig keys
|
|
97
|
+
*/
|
|
98
|
+
public get public_multisig_keys(): string[] {
|
|
99
|
+
const result: string[] = [];
|
|
100
|
+
|
|
101
|
+
this.calculated_multisig_keys.forEach((key) => {
|
|
102
|
+
result.push(key.publicKey);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns the private multisig keys
|
|
110
|
+
*/
|
|
111
|
+
public get private_multisig_keys(): string [] {
|
|
112
|
+
const result: string[] = [];
|
|
113
|
+
|
|
114
|
+
this.calculated_multisig_keys.forEach((key) => {
|
|
115
|
+
result.push(key.privateKey);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Calculates our multisig keys using the participant public spend keys
|
|
123
|
+
* @returns our multisig keys key pairs
|
|
124
|
+
*/
|
|
125
|
+
private get calculated_multisig_keys(): KeyPair[] {
|
|
126
|
+
if (this.m_threshold !== this.m_participants) {
|
|
127
|
+
const multisig_keys: KeyPair[] = [];
|
|
128
|
+
|
|
129
|
+
this.m_wallet_multisig_keys.forEach((multisig_key) => {
|
|
130
|
+
const keys =
|
|
131
|
+
TurtleCoinCrypto.calculateMultisigPrivateKeys(multisig_key.privateKey, this.m_participant_keys);
|
|
132
|
+
|
|
133
|
+
keys.forEach((key) => {
|
|
134
|
+
multisig_keys.push(new KeyPair(undefined, key));
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return multisig_keys;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throw new Error('Not a M:N instance');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Returns if the view information is ready
|
|
146
|
+
*/
|
|
147
|
+
private get isViewReady(): boolean {
|
|
148
|
+
return (this.m_currentParticipants === this.m_participants);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns if the spend information is ready
|
|
153
|
+
*/
|
|
154
|
+
private get isSpendReady(): boolean {
|
|
155
|
+
const loaded = (this.threshold === this.participants) ?
|
|
156
|
+
this.m_participant_keys.length + 1 :
|
|
157
|
+
this.m_participant_keys.length;
|
|
158
|
+
return (loaded === Multisig.requiredSigningKeys(this.threshold, this.participants));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Let's us know how many times participants must exchange information after the initial exchange
|
|
163
|
+
* @param threshold the number of participants required to construct a new transaction
|
|
164
|
+
* @param participants the wallet participants
|
|
165
|
+
* @returns the number of additional exchange rounds required
|
|
166
|
+
*/
|
|
167
|
+
public static exchangeRoundsRequired(threshold: number, participants: number): number {
|
|
168
|
+
return participants - threshold;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Initializes an initial multisig object using our address information
|
|
173
|
+
* @param wallet our base wallet used to create the wallet
|
|
174
|
+
* @param threshold the number of participants required to construct a new transaction
|
|
175
|
+
* @param participants the wallet participants
|
|
176
|
+
* @returns a new instance of the object
|
|
177
|
+
*/
|
|
178
|
+
public static fromAddress(wallet: Address, threshold: number, participants: number): Multisig {
|
|
179
|
+
if (!isValidThreshold(threshold, participants)) {
|
|
180
|
+
throw new Error('Threshold does not require a majority of participants');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const result = new Multisig();
|
|
184
|
+
|
|
185
|
+
if (!wallet.spend.isPaired) {
|
|
186
|
+
throw new Error('Must have both private and public spend keys');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!wallet.view.isPaired) {
|
|
190
|
+
throw new Error('Must have both private and public view keys');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
result.m_wallet_multisig_keys.push(wallet.spend);
|
|
194
|
+
|
|
195
|
+
result.m_multisig_keys.push(wallet.spend);
|
|
196
|
+
|
|
197
|
+
result.m_view_keys.push(wallet.view.privateKey);
|
|
198
|
+
|
|
199
|
+
result.m_threshold = threshold;
|
|
200
|
+
|
|
201
|
+
result.m_participants = participants;
|
|
202
|
+
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Initializes a multisig object using our previously generated multisig private keys
|
|
208
|
+
* @param multisig_private_keys the previously generated multisig private keys
|
|
209
|
+
* @param sharedPrivateViewKey the previously calculated view private key
|
|
210
|
+
* @param threshold the number of participants required to construct a new transaction
|
|
211
|
+
* @param participants the wallet participants
|
|
212
|
+
* @returns a new instance of the object
|
|
213
|
+
*/
|
|
214
|
+
public static fromMultisigKeys(
|
|
215
|
+
multisig_private_keys: string[],
|
|
216
|
+
sharedPrivateViewKey: string,
|
|
217
|
+
threshold: number,
|
|
218
|
+
participants: number,
|
|
219
|
+
): Multisig {
|
|
220
|
+
if (!isValidThreshold(threshold, participants)) {
|
|
221
|
+
throw new Error('Threshold does not require a majority of participants');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const result = new Multisig();
|
|
225
|
+
|
|
226
|
+
multisig_private_keys.forEach((key) => {
|
|
227
|
+
if (!TurtleCoinCrypto.checkScalar(key)) {
|
|
228
|
+
throw new Error('Found an invalid private key in the list of multisig private keys');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
result.m_multisig_keys.push(new KeyPair(undefined, key));
|
|
232
|
+
|
|
233
|
+
result.m_wallet_multisig_keys.push(new KeyPair(undefined, key));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (!TurtleCoinCrypto.checkScalar(sharedPrivateViewKey)) {
|
|
237
|
+
throw new Error('Private view key is not a valid private key');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
result.m_view_keys.push(sharedPrivateViewKey);
|
|
241
|
+
|
|
242
|
+
result.m_threshold = threshold;
|
|
243
|
+
|
|
244
|
+
result.m_participants = participants;
|
|
245
|
+
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Returns the total number of signing keys created using the M:N values supplied
|
|
251
|
+
* @param threshold the number of participants required to construct a new transaction
|
|
252
|
+
* @param participants the wallet participants
|
|
253
|
+
* @returns the total number of signing keys created
|
|
254
|
+
*/
|
|
255
|
+
public static requiredSigningKeys(threshold: number, participants: number): number {
|
|
256
|
+
return required_keys(threshold, participants);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Returns if the given M:N scheme is valid for this library
|
|
261
|
+
* @param threshold the number of participants required to construct a new transaction
|
|
262
|
+
* @param participants the wallet participants
|
|
263
|
+
* @returns if the given scheme is valid for this library
|
|
264
|
+
*/
|
|
265
|
+
public static isValidThreshold(threshold: number, participants: number): boolean {
|
|
266
|
+
return isValidThreshold(threshold, participants);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Restores a key image from partial key images
|
|
271
|
+
* @param publicEphemeral the key image public emphermal
|
|
272
|
+
* @param derivation the key input derivation
|
|
273
|
+
* @param outputIndex the key input output index
|
|
274
|
+
* @param partialKeyImages the partial key images
|
|
275
|
+
* @returns the restored key image
|
|
276
|
+
*/
|
|
277
|
+
public static async restoreKeyImage(
|
|
278
|
+
publicEphemeral: string,
|
|
279
|
+
derivation: string,
|
|
280
|
+
outputIndex: number,
|
|
281
|
+
partialKeyImages: string[],
|
|
282
|
+
): Promise<string> {
|
|
283
|
+
return TurtleCoinCrypto.restoreKeyImage(publicEphemeral, derivation, outputIndex, partialKeyImages);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private m_wallet_multisig_keys: KeyPair[] = [];
|
|
287
|
+
private m_multisig_keys: KeyPair[] = [];
|
|
288
|
+
private m_participant_keys: string[] = [];
|
|
289
|
+
private m_view_keys: string[] = [];
|
|
290
|
+
private m_threshold: number = 0;
|
|
291
|
+
private m_participants: number = 0;
|
|
292
|
+
private m_currentParticipants: number = 1;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Adds a participant to the multisig object
|
|
296
|
+
* Note: If this is an additional round of exchange, the publicSpendKeys should be the array of
|
|
297
|
+
* public multisig keys of the participant and we will not supply the private view key again.
|
|
298
|
+
* @param publicSpendKeys the participant spend key(s)
|
|
299
|
+
* @param [privateViewKey] the private view key of the participant
|
|
300
|
+
*/
|
|
301
|
+
public async addParticipant(
|
|
302
|
+
publicSpendKeys: string[] | string,
|
|
303
|
+
privateViewKey?: string,
|
|
304
|
+
): Promise<void> {
|
|
305
|
+
if (privateViewKey && !TurtleCoinCrypto.checkScalar(privateViewKey)) {
|
|
306
|
+
throw new Error('Private view key is not a valid private key');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (Array.isArray(publicSpendKeys) && publicSpendKeys.length > 1 && privateViewKey) {
|
|
310
|
+
throw new Error('Must not supply the private view key with the participant in subsequent rounds');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!Array.isArray(publicSpendKeys)) {
|
|
314
|
+
publicSpendKeys = [publicSpendKeys];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
publicSpendKeys.forEach((key) => {
|
|
318
|
+
if (!TurtleCoinCrypto.checkKey(key)) {
|
|
319
|
+
throw new Error('Found an invalid public spend key in the list');
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
if (privateViewKey && this.m_view_keys.indexOf(privateViewKey) === -1) {
|
|
324
|
+
this.m_view_keys.push(privateViewKey);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
publicSpendKeys.forEach((key) => {
|
|
328
|
+
if (this.m_participant_keys.indexOf(key) === -1) {
|
|
329
|
+
this.m_participant_keys.push(key);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
this.m_currentParticipants++;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Generates the partial key images for the given public ephemeral
|
|
338
|
+
* @param transactionHash the transaction hash containing the output used
|
|
339
|
+
* @param publicEphemeral the public ephemeral of the output
|
|
340
|
+
* @param outputIndex the index of the output in the transaction
|
|
341
|
+
* @returns the partial key images
|
|
342
|
+
*/
|
|
343
|
+
public async generatePartialKeyImages(
|
|
344
|
+
transactionHash: string,
|
|
345
|
+
publicEphemeral: string,
|
|
346
|
+
outputIndex: number,
|
|
347
|
+
): Promise<MultisigInterfaces.PartialKeyImage[]> {
|
|
348
|
+
const promises = [];
|
|
349
|
+
|
|
350
|
+
for (const multisigKey of this.m_multisig_keys) {
|
|
351
|
+
promises.push(TurtleCoinCrypto.generateKeyImage(publicEphemeral, multisigKey.privateKey));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const results = await Promise.all(promises);
|
|
355
|
+
|
|
356
|
+
const partialKeyImages: MultisigInterfaces.PartialKeyImage[] = [];
|
|
357
|
+
|
|
358
|
+
for (const result of results) {
|
|
359
|
+
partialKeyImages.push({
|
|
360
|
+
transactionHash,
|
|
361
|
+
outputIndex,
|
|
362
|
+
partialKeyImage: result,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return partialKeyImages;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Generates the partial signing keys for the given prepared transaction
|
|
371
|
+
* @param tx the prepared transaction
|
|
372
|
+
* @returns the partial signing keys
|
|
373
|
+
*/
|
|
374
|
+
public async generatePartialSigningKeys(
|
|
375
|
+
tx: Interfaces.PreparedTransaction,
|
|
376
|
+
): Promise<MultisigInterfaces.PartialSigningKey[]> {
|
|
377
|
+
const promises = [];
|
|
378
|
+
|
|
379
|
+
for (let i = 0; i < tx.transaction.signatures.length; i++) {
|
|
380
|
+
const realOutputIndex = getRealOutputIndex(tx, i);
|
|
381
|
+
|
|
382
|
+
for (const multisigKey of this.m_multisig_keys) {
|
|
383
|
+
promises.push(
|
|
384
|
+
generatePartialSigningKey(
|
|
385
|
+
tx.transaction.signatures[i][realOutputIndex], i, multisigKey.privateKey));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const results = await Promise.all(promises);
|
|
390
|
+
|
|
391
|
+
const partialSigningKeys: MultisigInterfaces.PartialSigningKey[] = [];
|
|
392
|
+
|
|
393
|
+
for (const result of results) {
|
|
394
|
+
partialSigningKeys.push({
|
|
395
|
+
transactionPrefixHash: tx.transaction.prefixHash,
|
|
396
|
+
index: result.index,
|
|
397
|
+
partialSigningKey: result.key,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return partialSigningKeys;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Restores the ring signatures of a prepared transaction using the supplied partial signing keys
|
|
406
|
+
* @param tx the prepared transaction
|
|
407
|
+
* @param partialSigningKeys the partial signing keys required for the signature scheme
|
|
408
|
+
* @returns the completed transaction
|
|
409
|
+
*/
|
|
410
|
+
public async completeTransaction(
|
|
411
|
+
tx: Interfaces.PreparedTransaction,
|
|
412
|
+
partialSigningKeys: MultisigInterfaces.PartialSigningKey[],
|
|
413
|
+
): Promise<Transaction> {
|
|
414
|
+
const promises = [];
|
|
415
|
+
|
|
416
|
+
for (let i = 0; i < tx.transaction.signatures.length; i++) {
|
|
417
|
+
const preparedRingSignature = getPreparedRingSignature(tx.signatureMeta, i);
|
|
418
|
+
const ringPartialKeys = getPartialSigningKeys(partialSigningKeys, i);
|
|
419
|
+
|
|
420
|
+
for (const partialKey of ringPartialKeys) {
|
|
421
|
+
promises.push(restoreRingSignatures(
|
|
422
|
+
preparedRingSignature.input.derivation,
|
|
423
|
+
preparedRingSignature.input.outputIndex,
|
|
424
|
+
ringPartialKeys,
|
|
425
|
+
preparedRingSignature.realOutputIndex,
|
|
426
|
+
preparedRingSignature.key,
|
|
427
|
+
tx.transaction.signatures[i],
|
|
428
|
+
i,
|
|
429
|
+
));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const results = await Promise.all(promises);
|
|
434
|
+
|
|
435
|
+
for (const result of results) {
|
|
436
|
+
tx.transaction.signatures[result.index] = result.sigs;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const prefixHash = tx.transaction.prefixHash;
|
|
440
|
+
|
|
441
|
+
const checkPromises = [];
|
|
442
|
+
|
|
443
|
+
for (let i = 0; i < tx.transaction.inputs.length; i++) {
|
|
444
|
+
checkPromises.push(checkRingSignatures(
|
|
445
|
+
prefixHash,
|
|
446
|
+
(tx.transaction.inputs[i] as TransactionInputs.KeyInput).keyImage,
|
|
447
|
+
getInputKeys(tx.signatureMeta, i),
|
|
448
|
+
tx.transaction.signatures[i],
|
|
449
|
+
));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const validSigs = await Promise.all(checkPromises);
|
|
453
|
+
|
|
454
|
+
for (const valid of validSigs) {
|
|
455
|
+
if (!valid) {
|
|
456
|
+
throw new Error('Could not complete ring signatures');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return tx.transaction;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** @ignore */
|
|
465
|
+
function isValidThreshold(threshold: number, participants: number): boolean {
|
|
466
|
+
return (!((threshold / participants) <= .5) && required_keys(threshold, participants) <= 2 ** 13);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** @ignore */
|
|
470
|
+
function required_keys(
|
|
471
|
+
threshold: number,
|
|
472
|
+
participants: number,
|
|
473
|
+
): number {
|
|
474
|
+
let result = participants;
|
|
475
|
+
|
|
476
|
+
const rounds = Multisig.exchangeRoundsRequired(threshold, participants);
|
|
477
|
+
|
|
478
|
+
for (let i = 0; i < rounds; i++) {
|
|
479
|
+
result = result - 1;
|
|
480
|
+
|
|
481
|
+
result = ((result ** 2) + result) / 2;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return result;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** @ignore */
|
|
488
|
+
async function generatePartialSigningKey(
|
|
489
|
+
preparedSignature: string,
|
|
490
|
+
index: number,
|
|
491
|
+
privateSpendKey: string,
|
|
492
|
+
): Promise<{ key: string, index: number }> {
|
|
493
|
+
const key = await TurtleCoinCrypto.generatePartialSigningKey(preparedSignature, privateSpendKey);
|
|
494
|
+
|
|
495
|
+
return {
|
|
496
|
+
key,
|
|
497
|
+
index,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** @ignore */
|
|
502
|
+
function getRealOutputIndex(
|
|
503
|
+
tx: Interfaces.PreparedTransaction,
|
|
504
|
+
index: number,
|
|
505
|
+
): number {
|
|
506
|
+
for (const sigs of tx.signatureMeta) {
|
|
507
|
+
if (sigs.index === index) {
|
|
508
|
+
return sigs.realOutputIndex;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
throw new Error('Could not find the real output index in the prepared ring signatures');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** @ignore */
|
|
516
|
+
function getPartialSigningKeys(
|
|
517
|
+
partialSigningKeys: MultisigInterfaces.PartialSigningKey[],
|
|
518
|
+
index: number,
|
|
519
|
+
): MultisigInterfaces.PartialSigningKey[] {
|
|
520
|
+
const results: MultisigInterfaces.PartialSigningKey[] = [];
|
|
521
|
+
|
|
522
|
+
for (const partialSigningKey of partialSigningKeys) {
|
|
523
|
+
if (partialSigningKey.index === index) {
|
|
524
|
+
results.push(partialSigningKey);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return results;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** @ignore */
|
|
532
|
+
function getPreparedRingSignature(
|
|
533
|
+
preparedRingSignatures: Interfaces.PreparedRingSignature[],
|
|
534
|
+
index: number,
|
|
535
|
+
): Interfaces.PreparedRingSignature {
|
|
536
|
+
for (const preparedRingSignature of preparedRingSignatures) {
|
|
537
|
+
if (preparedRingSignature.index === index) {
|
|
538
|
+
return preparedRingSignature;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
throw new Error('Prepared ring signature not found at specified index');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** @ignore */
|
|
546
|
+
async function restoreRingSignatures(
|
|
547
|
+
derivation: string,
|
|
548
|
+
outputIndex: number,
|
|
549
|
+
partialSigningKeys: MultisigInterfaces.PartialSigningKey[],
|
|
550
|
+
realOutputIndex: number,
|
|
551
|
+
key: string,
|
|
552
|
+
signatures: string[],
|
|
553
|
+
index: number,
|
|
554
|
+
): Promise<{ sigs: string[], index: number }> {
|
|
555
|
+
const keys: string[] = [];
|
|
556
|
+
|
|
557
|
+
for (const partialSigningKey of partialSigningKeys) {
|
|
558
|
+
if (partialSigningKey.index !== index) {
|
|
559
|
+
throw new Error('invalid partial signing key supplied');
|
|
560
|
+
}
|
|
561
|
+
keys.push(partialSigningKey.partialSigningKey);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const sigs = await TurtleCoinCrypto.restoreRingSignatures(
|
|
565
|
+
derivation,
|
|
566
|
+
outputIndex,
|
|
567
|
+
keys,
|
|
568
|
+
realOutputIndex,
|
|
569
|
+
key,
|
|
570
|
+
signatures,
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
return {
|
|
574
|
+
sigs,
|
|
575
|
+
index,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** @ignore */
|
|
580
|
+
function getInputKeys(preparedSignatures: Interfaces.PreparedRingSignature[], index: number): string[] {
|
|
581
|
+
for (const meta of preparedSignatures) {
|
|
582
|
+
if (meta.index === index) {
|
|
583
|
+
if (meta.inputKeys) {
|
|
584
|
+
return meta.inputKeys;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
throw new Error('Could not locate input keys in the prepared signatures');
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** @ignore */
|
|
593
|
+
async function checkRingSignatures(
|
|
594
|
+
hash: string,
|
|
595
|
+
keyImage: string,
|
|
596
|
+
publicKeys: string[],
|
|
597
|
+
signatures: string[],
|
|
598
|
+
): Promise<boolean> {
|
|
599
|
+
return TurtleCoinCrypto.checkRingSignatures(hash, keyImage, publicKeys, signatures);
|
|
600
|
+
}
|