@buildonspark/spark-sdk 0.0.15 → 0.0.16
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/dist/services/wallet-config.d.ts +1 -0
- package/dist/services/wallet-config.js +1 -0
- package/dist/services/wallet-config.js.map +1 -1
- package/dist/spark-sdk.d.ts +1 -1
- package/dist/spark-sdk.js +3 -3
- package/dist/spark-sdk.js.map +1 -1
- package/package.json +4 -3
- package/src/examples/example.js +247 -0
- package/src/examples/example.ts +207 -0
- package/src/graphql/client.ts +282 -0
- package/src/graphql/mutations/CompleteCoopExit.ts +19 -0
- package/src/graphql/mutations/CompleteLeavesSwap.ts +17 -0
- package/src/graphql/mutations/RequestCoopExit.ts +20 -0
- package/src/graphql/mutations/RequestLightningReceive.ts +26 -0
- package/src/graphql/mutations/RequestLightningSend.ts +17 -0
- package/src/graphql/mutations/RequestSwapLeaves.ts +24 -0
- package/src/graphql/objects/BitcoinNetwork.ts +22 -0
- package/src/graphql/objects/CompleteCoopExitInput.ts +41 -0
- package/src/graphql/objects/CompleteCoopExitOutput.ts +45 -0
- package/src/graphql/objects/CompleteLeavesSwapInput.ts +45 -0
- package/src/graphql/objects/CompleteLeavesSwapOutput.ts +45 -0
- package/src/graphql/objects/CompleteSeedReleaseInput.ts +41 -0
- package/src/graphql/objects/CompleteSeedReleaseOutput.ts +43 -0
- package/src/graphql/objects/Connection.ts +90 -0
- package/src/graphql/objects/CoopExitFeeEstimateInput.ts +41 -0
- package/src/graphql/objects/CoopExitFeeEstimateOutput.ts +52 -0
- package/src/graphql/objects/CoopExitRequest.ts +118 -0
- package/src/graphql/objects/CurrencyAmount.ts +74 -0
- package/src/graphql/objects/CurrencyUnit.ts +32 -0
- package/src/graphql/objects/Entity.ts +202 -0
- package/src/graphql/objects/GetChallengeInput.ts +37 -0
- package/src/graphql/objects/GetChallengeOutput.ts +43 -0
- package/src/graphql/objects/Invoice.ts +83 -0
- package/src/graphql/objects/Leaf.ts +59 -0
- package/src/graphql/objects/LeavesSwapFeeEstimateInput.ts +37 -0
- package/src/graphql/objects/LeavesSwapFeeEstimateOutput.ts +52 -0
- package/src/graphql/objects/LeavesSwapRequest.ts +192 -0
- package/src/graphql/objects/LightningReceiveFeeEstimateInput.ts +41 -0
- package/src/graphql/objects/LightningReceiveFeeEstimateOutput.ts +52 -0
- package/src/graphql/objects/LightningReceiveRequest.ts +147 -0
- package/src/graphql/objects/LightningReceiveRequestStatus.ts +34 -0
- package/src/graphql/objects/LightningSendFeeEstimateInput.ts +37 -0
- package/src/graphql/objects/LightningSendFeeEstimateOutput.ts +52 -0
- package/src/graphql/objects/LightningSendRequest.ts +134 -0
- package/src/graphql/objects/LightningSendRequestStatus.ts +28 -0
- package/src/graphql/objects/NotifyReceiverTransferInput.ts +41 -0
- package/src/graphql/objects/PageInfo.ts +58 -0
- package/src/graphql/objects/Provider.ts +41 -0
- package/src/graphql/objects/RequestCoopExitInput.ts +41 -0
- package/src/graphql/objects/RequestCoopExitOutput.ts +45 -0
- package/src/graphql/objects/RequestLeavesSwapInput.ts +55 -0
- package/src/graphql/objects/RequestLeavesSwapOutput.ts +45 -0
- package/src/graphql/objects/RequestLightningReceiveInput.ts +58 -0
- package/src/graphql/objects/RequestLightningReceiveOutput.ts +45 -0
- package/src/graphql/objects/RequestLightningSendInput.ts +41 -0
- package/src/graphql/objects/RequestLightningSendOutput.ts +45 -0
- package/src/graphql/objects/SparkCoopExitRequestStatus.ts +20 -0
- package/src/graphql/objects/SparkLeavesSwapRequestStatus.ts +20 -0
- package/src/graphql/objects/SparkTransferToLeavesConnection.ts +79 -0
- package/src/graphql/objects/SparkWalletUser.ts +86 -0
- package/src/graphql/objects/StartSeedReleaseInput.ts +37 -0
- package/src/graphql/objects/SwapLeaf.ts +53 -0
- package/src/graphql/objects/Transfer.ts +98 -0
- package/src/graphql/objects/UserLeafInput.ts +28 -0
- package/src/graphql/objects/VerifyChallengeInput.ts +51 -0
- package/src/graphql/objects/VerifyChallengeOutput.ts +43 -0
- package/src/graphql/objects/WalletUserIdentityPublicKeyInput.ts +37 -0
- package/src/graphql/objects/WalletUserIdentityPublicKeyOutput.ts +43 -0
- package/src/graphql/objects/index.ts +67 -0
- package/src/graphql/queries/CoopExitFeeEstimate.ts +18 -0
- package/src/graphql/queries/CurrentUser.ts +10 -0
- package/src/graphql/queries/LightningReceiveFeeEstimate.ts +18 -0
- package/src/graphql/queries/LightningSendFeeEstimate.ts +16 -0
- package/src/proto/common.ts +431 -0
- package/src/proto/google/protobuf/descriptor.ts +6625 -0
- package/src/proto/google/protobuf/duration.ts +197 -0
- package/src/proto/google/protobuf/empty.ts +83 -0
- package/src/proto/google/protobuf/timestamp.ts +226 -0
- package/src/proto/mock.ts +151 -0
- package/src/proto/spark.ts +12727 -0
- package/src/proto/spark_authn.ts +673 -0
- package/src/proto/validate/validate.ts +6047 -0
- package/src/services/config.ts +71 -0
- package/src/services/connection.ts +264 -0
- package/src/services/coop-exit.ts +190 -0
- package/src/services/deposit.ts +327 -0
- package/src/services/lightning.ts +341 -0
- package/src/services/lrc20.ts +42 -0
- package/src/services/token-transactions.ts +499 -0
- package/src/services/transfer.ts +1188 -0
- package/src/services/tree-creation.ts +618 -0
- package/src/services/wallet-config.ts +141 -0
- package/src/signer/signer.ts +531 -0
- package/src/spark-sdk.ts +1644 -0
- package/src/tests/adaptor-signature.test.ts +64 -0
- package/src/tests/bitcoin.test.ts +122 -0
- package/src/tests/coop-exit.test.ts +233 -0
- package/src/tests/deposit.test.ts +98 -0
- package/src/tests/keys.test.ts +82 -0
- package/src/tests/lightning.test.ts +307 -0
- package/src/tests/secret-sharing.test.ts +63 -0
- package/src/tests/swap.test.ts +252 -0
- package/src/tests/test-util.ts +92 -0
- package/src/tests/tokens.test.ts +47 -0
- package/src/tests/transfer.test.ts +371 -0
- package/src/tests/tree-creation.test.ts +56 -0
- package/src/tests/utils/spark-testing-wallet.ts +37 -0
- package/src/tests/utils/test-faucet.ts +257 -0
- package/src/types/grpc.ts +8 -0
- package/src/types/index.ts +3 -0
- package/src/utils/adaptor-signature.ts +189 -0
- package/src/utils/bitcoin.ts +138 -0
- package/src/utils/crypto.ts +14 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/keys.ts +92 -0
- package/src/utils/mempool.ts +42 -0
- package/src/utils/network.ts +70 -0
- package/src/utils/proof.ts +17 -0
- package/src/utils/response-validation.ts +26 -0
- package/src/utils/secret-sharing.ts +263 -0
- package/src/utils/signing.ts +96 -0
- package/src/utils/token-hashing.ts +163 -0
- package/src/utils/token-keyshares.ts +31 -0
- package/src/utils/token-transactions.ts +71 -0
- package/src/utils/transaction.ts +45 -0
- package/src/utils/wasm-wrapper.ts +57 -0
- package/src/utils/wasm.ts +154 -0
- package/src/wasm/spark_bindings.d.ts +208 -0
- package/src/wasm/spark_bindings.js +1161 -0
- package/src/wasm/spark_bindings_bg.wasm +0 -0
- package/src/wasm/spark_bindings_bg.wasm.d.ts +136 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { afterEach, beforeAll, describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { hexToBytes } from "@noble/curves/abstract/utils";
|
|
3
|
+
import { equalBytes, sha256 } from "@scure/btc-signer/utils";
|
|
4
|
+
import { TransferStatus } from "../proto/spark.js";
|
|
5
|
+
import { WalletConfigService } from "../services/config.js";
|
|
6
|
+
import { ConnectionManager } from "../services/connection.js";
|
|
7
|
+
import { LightningService } from "../services/lightning.js";
|
|
8
|
+
import { LeafKeyTweak, TransferService } from "../services/transfer.js";
|
|
9
|
+
import { createNewTree, getTestWalletConfig } from "./test-util.js";
|
|
10
|
+
import { SparkWalletTesting } from "./utils/spark-testing-wallet.js";
|
|
11
|
+
import { BitcoinFaucet } from "./utils/test-faucet.js";
|
|
12
|
+
|
|
13
|
+
async function cleanUp() {
|
|
14
|
+
const config = getTestWalletConfig();
|
|
15
|
+
|
|
16
|
+
const preimage = hexToBytes(
|
|
17
|
+
"2d059c3ede82a107aa1452c0bea47759be3c5c6e5342be6a310f6c3a907d9f4c",
|
|
18
|
+
);
|
|
19
|
+
const paymentHash = sha256(preimage);
|
|
20
|
+
|
|
21
|
+
for (const operator of Object.values(config.signingOperators!)) {
|
|
22
|
+
const client = ConnectionManager.createMockClient(operator!.address);
|
|
23
|
+
await client.clean_up_preimage_share({
|
|
24
|
+
paymentHash,
|
|
25
|
+
});
|
|
26
|
+
client.close();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const fakeInvoiceCreator = async () => {
|
|
31
|
+
return "lnbcrt123450n1pnj6uf4pp5l26hsdxssmr52vd4xmn5xran7puzx34hpr6uevaq7ta0ayzrp8esdqqcqzpgxqyz5vqrzjqtr2vd60g57hu63rdqk87u3clac6jlfhej4kldrrjvfcw3mphcw8sqqqqzp3jlj6zyqqqqqqqqqqqqqq9qsp5w22fd8aqn7sdum7hxdf59ptgk322fkv589ejxjltngvgehlcqcyq9qxpqysgqvykwsxdx64qrj0s5pgcgygmrpj8w25jsjgltwn09yp24l9nvghe3dl3y0ycy70ksrlqmcn42hxn24e0ucuy3g9fjltudvhv4lrhhamgq3stqgp";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe("LightningService", () => {
|
|
35
|
+
let userWallet: SparkWalletTesting;
|
|
36
|
+
let userConfig: WalletConfigService;
|
|
37
|
+
let lightningService: LightningService;
|
|
38
|
+
let transferService: TransferService;
|
|
39
|
+
|
|
40
|
+
let sspWallet: SparkWalletTesting;
|
|
41
|
+
let sspConfig: WalletConfigService;
|
|
42
|
+
let sspLightningService: LightningService;
|
|
43
|
+
let sspTransferService: TransferService;
|
|
44
|
+
|
|
45
|
+
// Skip all tests if running in GitHub Actions
|
|
46
|
+
const testFn = process.env.GITHUB_ACTIONS ? it.skip : it;
|
|
47
|
+
|
|
48
|
+
beforeAll(async () => {
|
|
49
|
+
const { wallet: wallet1 } = await SparkWalletTesting.create({
|
|
50
|
+
options: {
|
|
51
|
+
network: "LOCAL",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
userWallet = wallet1;
|
|
56
|
+
|
|
57
|
+
userConfig = new WalletConfigService(
|
|
58
|
+
{
|
|
59
|
+
network: "LOCAL",
|
|
60
|
+
},
|
|
61
|
+
userWallet.getSigner(),
|
|
62
|
+
);
|
|
63
|
+
const connectionManager = new ConnectionManager(userConfig);
|
|
64
|
+
lightningService = new LightningService(userConfig, connectionManager);
|
|
65
|
+
transferService = new TransferService(userConfig, connectionManager);
|
|
66
|
+
|
|
67
|
+
const { wallet: wallet2 } = await SparkWalletTesting.create({
|
|
68
|
+
options: {
|
|
69
|
+
network: "LOCAL",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
sspWallet = wallet2;
|
|
74
|
+
|
|
75
|
+
sspConfig = new WalletConfigService(
|
|
76
|
+
{
|
|
77
|
+
network: "LOCAL",
|
|
78
|
+
},
|
|
79
|
+
sspWallet.getSigner(),
|
|
80
|
+
);
|
|
81
|
+
const sspConnectionManager = new ConnectionManager(sspConfig);
|
|
82
|
+
sspLightningService = new LightningService(sspConfig, sspConnectionManager);
|
|
83
|
+
sspTransferService = new TransferService(sspConfig, sspConnectionManager);
|
|
84
|
+
});
|
|
85
|
+
afterEach(async () => {
|
|
86
|
+
await cleanUp();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
testFn("should create an invoice", async () => {
|
|
90
|
+
const preimage = hexToBytes(
|
|
91
|
+
"2d059c3ede82a107aa1452c0bea47759be3c5c6e5342be6a310f6c3a907d9f4c",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const invoice = await lightningService.createLightningInvoiceWithPreImage({
|
|
95
|
+
invoiceCreator: fakeInvoiceCreator,
|
|
96
|
+
amountSats: 100,
|
|
97
|
+
memo: "test",
|
|
98
|
+
preimage,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(invoice).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
testFn(
|
|
105
|
+
"test receive lightning payment",
|
|
106
|
+
async () => {
|
|
107
|
+
const faucet = new BitcoinFaucet(
|
|
108
|
+
"http://127.0.0.1:18443",
|
|
109
|
+
"admin1",
|
|
110
|
+
"123",
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const preimage = hexToBytes(
|
|
114
|
+
"2d059c3ede82a107aa1452c0bea47759be3c5c6e5342be6a310f6c3a907d9f4c",
|
|
115
|
+
);
|
|
116
|
+
const paymentHash = sha256(preimage);
|
|
117
|
+
|
|
118
|
+
const invoice = await lightningService.createLightningInvoiceWithPreImage(
|
|
119
|
+
{
|
|
120
|
+
invoiceCreator: fakeInvoiceCreator,
|
|
121
|
+
amountSats: 100,
|
|
122
|
+
memo: "test",
|
|
123
|
+
preimage,
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(invoice).toBeDefined();
|
|
128
|
+
|
|
129
|
+
const sspLeafPubKey = await sspWallet.getSigner().generatePublicKey();
|
|
130
|
+
const nodeToSend = await createNewTree(
|
|
131
|
+
sspWallet,
|
|
132
|
+
sspLeafPubKey,
|
|
133
|
+
faucet,
|
|
134
|
+
12345n,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const newLeafPubKey = await sspWallet
|
|
138
|
+
.getSigner()
|
|
139
|
+
.generatePublicKey(sha256("1"));
|
|
140
|
+
|
|
141
|
+
const leaves: LeafKeyTweak[] = [
|
|
142
|
+
{
|
|
143
|
+
leaf: nodeToSend,
|
|
144
|
+
signingPubKey: sspLeafPubKey,
|
|
145
|
+
newSigningPubKey: newLeafPubKey,
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
const response = await sspLightningService.swapNodesForPreimage({
|
|
150
|
+
leaves,
|
|
151
|
+
receiverIdentityPubkey: await userConfig.signer.getIdentityPublicKey(),
|
|
152
|
+
paymentHash,
|
|
153
|
+
isInboundPayment: true,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(equalBytes(response.preimage, preimage)).toBe(true);
|
|
157
|
+
|
|
158
|
+
const senderTransfer = response.transfer;
|
|
159
|
+
|
|
160
|
+
expect(senderTransfer).toBeDefined();
|
|
161
|
+
|
|
162
|
+
const transfer = await sspTransferService.sendTransferTweakKey(
|
|
163
|
+
senderTransfer!,
|
|
164
|
+
leaves,
|
|
165
|
+
new Map(),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(transfer.status).toEqual(
|
|
169
|
+
TransferStatus.TRANSFER_STATUS_SENDER_KEY_TWEAKED,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const pendingTransfer = await transferService.queryPendingTransfers();
|
|
173
|
+
|
|
174
|
+
expect(pendingTransfer.transfers.length).toBe(1);
|
|
175
|
+
|
|
176
|
+
const receiverTransfer = pendingTransfer.transfers[0];
|
|
177
|
+
|
|
178
|
+
expect(receiverTransfer!.id).toEqual(senderTransfer!.id);
|
|
179
|
+
|
|
180
|
+
const leafPrivKeyMap = await transferService.verifyPendingTransfer(
|
|
181
|
+
receiverTransfer!,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(leafPrivKeyMap.size).toBe(1);
|
|
185
|
+
expect(leafPrivKeyMap.has(nodeToSend.id)).toBe(true);
|
|
186
|
+
expect(
|
|
187
|
+
equalBytes(leafPrivKeyMap.get(nodeToSend.id)!, newLeafPubKey),
|
|
188
|
+
).toBe(true);
|
|
189
|
+
|
|
190
|
+
const finalLeafPubKey = await userWallet.getSigner().generatePublicKey();
|
|
191
|
+
|
|
192
|
+
const leaf = receiverTransfer!.leaves[0]!.leaf;
|
|
193
|
+
expect(leaf).toBeDefined();
|
|
194
|
+
|
|
195
|
+
const claimingNode = {
|
|
196
|
+
leaf: leaf!,
|
|
197
|
+
signingPubKey: newLeafPubKey,
|
|
198
|
+
newSigningPubKey: finalLeafPubKey,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
await transferService.claimTransfer(receiverTransfer!, [claimingNode]);
|
|
202
|
+
},
|
|
203
|
+
60000,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
testFn(
|
|
207
|
+
"test send lightning payment",
|
|
208
|
+
async () => {
|
|
209
|
+
const faucet = new BitcoinFaucet(
|
|
210
|
+
"http://127.0.0.1:18443",
|
|
211
|
+
"admin1",
|
|
212
|
+
"123",
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const preimage = hexToBytes(
|
|
216
|
+
"2d059c3ede82a107aa1452c0bea47759be3c5c6e5342be6a310f6c3a907d9f4c",
|
|
217
|
+
);
|
|
218
|
+
const paymentHash = sha256(preimage);
|
|
219
|
+
|
|
220
|
+
const userLeafPubKey = await userWallet
|
|
221
|
+
.getSigner()
|
|
222
|
+
.generatePublicKey(sha256("1"));
|
|
223
|
+
const nodeToSend = await createNewTree(
|
|
224
|
+
userWallet,
|
|
225
|
+
userLeafPubKey,
|
|
226
|
+
faucet,
|
|
227
|
+
12345n,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const newLeafPubKey = await userWallet
|
|
231
|
+
.getSigner()
|
|
232
|
+
.generatePublicKey(sha256("2"));
|
|
233
|
+
|
|
234
|
+
const leaves: LeafKeyTweak[] = [
|
|
235
|
+
{
|
|
236
|
+
leaf: nodeToSend,
|
|
237
|
+
signingPubKey: userLeafPubKey,
|
|
238
|
+
newSigningPubKey: newLeafPubKey,
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
const response = await lightningService.swapNodesForPreimage({
|
|
243
|
+
leaves,
|
|
244
|
+
receiverIdentityPubkey: await sspConfig.signer.getIdentityPublicKey(),
|
|
245
|
+
paymentHash,
|
|
246
|
+
isInboundPayment: false,
|
|
247
|
+
invoiceString: await fakeInvoiceCreator(),
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(response.transfer).toBeDefined();
|
|
251
|
+
|
|
252
|
+
// const refunds = await sspLightningService.queryUserSignedRefunds(
|
|
253
|
+
// paymentHash
|
|
254
|
+
// );
|
|
255
|
+
|
|
256
|
+
// let totalValue = 0n;
|
|
257
|
+
// for (const refund of refunds) {
|
|
258
|
+
// const value = sspLightningService.validateUserSignedRefund(refund);
|
|
259
|
+
// totalValue += value;
|
|
260
|
+
// }
|
|
261
|
+
|
|
262
|
+
// expect(totalValue).toBe(12345n);
|
|
263
|
+
|
|
264
|
+
const transfer = await transferService.sendTransferTweakKey(
|
|
265
|
+
response.transfer!,
|
|
266
|
+
leaves,
|
|
267
|
+
new Map(),
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
expect(transfer.status).toEqual(
|
|
271
|
+
TransferStatus.TRANSFER_STATUS_SENDER_KEY_TWEAK_PENDING,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const receiverTransfer =
|
|
275
|
+
await sspLightningService.providePreimage(preimage);
|
|
276
|
+
|
|
277
|
+
expect(receiverTransfer.status).toEqual(
|
|
278
|
+
TransferStatus.TRANSFER_STATUS_SENDER_KEY_TWEAKED,
|
|
279
|
+
);
|
|
280
|
+
expect(receiverTransfer.id).toEqual(transfer.id);
|
|
281
|
+
|
|
282
|
+
const leafPrivKeyMap =
|
|
283
|
+
await sspTransferService.verifyPendingTransfer(receiverTransfer);
|
|
284
|
+
|
|
285
|
+
expect(leafPrivKeyMap.size).toBe(1);
|
|
286
|
+
expect(leafPrivKeyMap.has(nodeToSend.id)).toBe(true);
|
|
287
|
+
expect(
|
|
288
|
+
equalBytes(leafPrivKeyMap.get(nodeToSend.id)!, newLeafPubKey),
|
|
289
|
+
).toBe(true);
|
|
290
|
+
|
|
291
|
+
const finalLeafPubKey = await sspWallet
|
|
292
|
+
.getSigner()
|
|
293
|
+
.generatePublicKey(sha256("2"));
|
|
294
|
+
|
|
295
|
+
expect(receiverTransfer.leaves[0]!.leaf).toBeDefined();
|
|
296
|
+
|
|
297
|
+
const claimingNode = {
|
|
298
|
+
leaf: receiverTransfer.leaves[0]!.leaf!,
|
|
299
|
+
signingPubKey: newLeafPubKey,
|
|
300
|
+
newSigningPubKey: finalLeafPubKey,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
await sspTransferService.claimTransfer(receiverTransfer, [claimingNode]);
|
|
304
|
+
},
|
|
305
|
+
60000,
|
|
306
|
+
);
|
|
307
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
3
|
+
import {
|
|
4
|
+
modInverse,
|
|
5
|
+
recoverSecret,
|
|
6
|
+
splitSecretWithProofs,
|
|
7
|
+
validateShare,
|
|
8
|
+
} from "../utils/secret-sharing.js";
|
|
9
|
+
|
|
10
|
+
describe("Secret Sharing", () => {
|
|
11
|
+
describe("modInverse", () => {
|
|
12
|
+
it("should correctly calculate modular multiplicative inverse", () => {
|
|
13
|
+
// Test cases: [a, m, expected]
|
|
14
|
+
const testCases: [bigint, bigint, bigint][] = [
|
|
15
|
+
[3n, 11n, 4n], // 3 * 4 ≡ 1 (mod 11)
|
|
16
|
+
[10n, 17n, 12n], // 10 * 12 ≡ 1 (mod 17)
|
|
17
|
+
[7n, 13n, 2n], // 7 * 2 ≡ 1 (mod 13)
|
|
18
|
+
[-1n, secp256k1.CURVE.n, secp256k1.CURVE.n - 1n],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const [a, m, expected] of testCases) {
|
|
22
|
+
const result = modInverse(a, m);
|
|
23
|
+
expect(result).toBe(expected);
|
|
24
|
+
|
|
25
|
+
// Normalize the result of (a * result) before taking modulo
|
|
26
|
+
const product = a * result;
|
|
27
|
+
const normalizedProduct = ((product % m) + m) % m;
|
|
28
|
+
expect(normalizedProduct).toBe(1n);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should throw error when modular inverse doesn't exist", () => {
|
|
33
|
+
expect(() => modInverse(4n, 8n)).toThrow(
|
|
34
|
+
"Modular inverse does not exist",
|
|
35
|
+
);
|
|
36
|
+
expect(() => modInverse(6n, 9n)).toThrow(
|
|
37
|
+
"Modular inverse does not exist",
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("test secret sharing", () => {
|
|
43
|
+
const fieldModulus = secp256k1.CURVE.n;
|
|
44
|
+
const secret =
|
|
45
|
+
56223216183876340914672117764605975762373003965917245943571257601961255596156n;
|
|
46
|
+
const threshold = 3;
|
|
47
|
+
const numberOfShares = 5;
|
|
48
|
+
|
|
49
|
+
const shares = splitSecretWithProofs(
|
|
50
|
+
secret,
|
|
51
|
+
fieldModulus,
|
|
52
|
+
threshold,
|
|
53
|
+
numberOfShares,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
for (const share of shares) {
|
|
57
|
+
validateShare(share);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const recoveredSecret = recoverSecret(shares.slice(0, threshold));
|
|
61
|
+
expect(recoveredSecret).toBe(secret);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { equalBytes, hexToBytes } from "@noble/curves/abstract/utils";
|
|
3
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
4
|
+
import { sha256 } from "@scure/btc-signer/utils";
|
|
5
|
+
import { WalletConfigService } from "../services/config.js";
|
|
6
|
+
import { ConnectionManager } from "../services/connection.js";
|
|
7
|
+
import { LeafKeyTweak, TransferService } from "../services/transfer.js";
|
|
8
|
+
import {
|
|
9
|
+
applyAdaptorToSignature,
|
|
10
|
+
generateAdaptorFromSignature,
|
|
11
|
+
} from "../utils/adaptor-signature.js";
|
|
12
|
+
import {
|
|
13
|
+
computeTaprootKeyNoScript,
|
|
14
|
+
getSigHashFromTx,
|
|
15
|
+
} from "../utils/bitcoin.js";
|
|
16
|
+
import { createNewTree } from "./test-util.js";
|
|
17
|
+
import { SparkWalletTesting } from "./utils/spark-testing-wallet.js";
|
|
18
|
+
import { BitcoinFaucet } from "./utils/test-faucet.js";
|
|
19
|
+
|
|
20
|
+
describe("swap", () => {
|
|
21
|
+
const testFn = process.env.GITHUB_ACTIONS ? it.skip : it;
|
|
22
|
+
|
|
23
|
+
testFn(
|
|
24
|
+
"test swap",
|
|
25
|
+
async () => {
|
|
26
|
+
const faucet = new BitcoinFaucet(
|
|
27
|
+
"http://127.0.0.1:18443",
|
|
28
|
+
"admin1",
|
|
29
|
+
"123",
|
|
30
|
+
);
|
|
31
|
+
// Initiate sender
|
|
32
|
+
const { wallet: senderWallet } = await SparkWalletTesting.create({
|
|
33
|
+
options: {
|
|
34
|
+
network: "LOCAL",
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const senderPubkey = await senderWallet.getIdentityPublicKey();
|
|
38
|
+
|
|
39
|
+
const senderConfig = new WalletConfigService(
|
|
40
|
+
{
|
|
41
|
+
network: "LOCAL",
|
|
42
|
+
},
|
|
43
|
+
senderWallet.getSigner(),
|
|
44
|
+
);
|
|
45
|
+
const senderConnectionManager = new ConnectionManager(senderConfig);
|
|
46
|
+
const senderTransferService = new TransferService(
|
|
47
|
+
senderConfig,
|
|
48
|
+
senderConnectionManager,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Initiate receiver
|
|
52
|
+
const { wallet: receiverWallet } = await SparkWalletTesting.create({
|
|
53
|
+
options: {
|
|
54
|
+
network: "LOCAL",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const receiverPubkey = await receiverWallet.getIdentityPublicKey();
|
|
58
|
+
|
|
59
|
+
const receiverConfig = new WalletConfigService(
|
|
60
|
+
{
|
|
61
|
+
network: "LOCAL",
|
|
62
|
+
},
|
|
63
|
+
receiverWallet.getSigner(),
|
|
64
|
+
);
|
|
65
|
+
const receiverConnectionManager = new ConnectionManager(receiverConfig);
|
|
66
|
+
const receiverTransferService = new TransferService(
|
|
67
|
+
receiverConfig,
|
|
68
|
+
receiverConnectionManager,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const senderLeafPubKey = await senderWallet
|
|
72
|
+
.getSigner()
|
|
73
|
+
.generatePublicKey();
|
|
74
|
+
const senderRootNode = await createNewTree(
|
|
75
|
+
senderWallet,
|
|
76
|
+
senderLeafPubKey,
|
|
77
|
+
faucet,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const receiverLeafPubKey = await receiverWallet
|
|
81
|
+
.getSigner()
|
|
82
|
+
.generatePublicKey();
|
|
83
|
+
const receiverRootNode = await createNewTree(
|
|
84
|
+
receiverWallet,
|
|
85
|
+
receiverLeafPubKey,
|
|
86
|
+
faucet,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Sender initiates transfer
|
|
90
|
+
const senderNewLeafPubKey = await senderWallet
|
|
91
|
+
.getSigner()
|
|
92
|
+
.generatePublicKey(sha256("1"));
|
|
93
|
+
const senderTransferNode: LeafKeyTweak = {
|
|
94
|
+
leaf: senderRootNode,
|
|
95
|
+
signingPubKey: senderLeafPubKey,
|
|
96
|
+
newSigningPubKey: senderNewLeafPubKey,
|
|
97
|
+
};
|
|
98
|
+
const senderLeavesToTransfer = [senderTransferNode];
|
|
99
|
+
|
|
100
|
+
// Get signature for refunds (normal flow)
|
|
101
|
+
const {
|
|
102
|
+
transfer: senderTransfer,
|
|
103
|
+
signatureMap: senderRefundSignatureMap,
|
|
104
|
+
leafDataMap: senderLeafDataMap,
|
|
105
|
+
} = await senderTransferService.sendTransferSignRefund(
|
|
106
|
+
senderLeavesToTransfer,
|
|
107
|
+
hexToBytes(receiverPubkey),
|
|
108
|
+
new Date(Date.now() + 10 * 60 * 1000),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(senderRefundSignatureMap.size).toBe(1);
|
|
112
|
+
const senderSignature = senderRefundSignatureMap.get(senderRootNode.id);
|
|
113
|
+
expect(senderSignature).toBeDefined();
|
|
114
|
+
expect(senderLeafDataMap.size).toBe(1);
|
|
115
|
+
|
|
116
|
+
const { adaptorPrivateKey, adaptorSignature } =
|
|
117
|
+
generateAdaptorFromSignature(senderSignature!);
|
|
118
|
+
const adaptorPubKey = secp256k1.getPublicKey(adaptorPrivateKey);
|
|
119
|
+
|
|
120
|
+
const receiverNewLeafPubKey = await receiverWallet
|
|
121
|
+
.getSigner()
|
|
122
|
+
.generatePublicKey(sha256("1"));
|
|
123
|
+
|
|
124
|
+
const receiverTransferNode: LeafKeyTweak = {
|
|
125
|
+
leaf: receiverRootNode,
|
|
126
|
+
signingPubKey: receiverLeafPubKey,
|
|
127
|
+
newSigningPubKey: receiverNewLeafPubKey,
|
|
128
|
+
};
|
|
129
|
+
const receiverLeavesToTransfer = [receiverTransferNode];
|
|
130
|
+
|
|
131
|
+
const {
|
|
132
|
+
transfer: receiverTransfer,
|
|
133
|
+
signatureMap: receiverRefundSignatureMap,
|
|
134
|
+
leafDataMap: receiverLeafDataMap,
|
|
135
|
+
signingResults: operatorSigningResults,
|
|
136
|
+
} = await receiverTransferService.sendSwapSignRefund(
|
|
137
|
+
receiverLeavesToTransfer,
|
|
138
|
+
hexToBytes(senderPubkey),
|
|
139
|
+
new Date(Date.now() + 10 * 60 * 1000),
|
|
140
|
+
adaptorPubKey,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const newReceiverRefundSignatureMap = new Map<string, Uint8Array>();
|
|
144
|
+
for (const [nodeId, signature] of receiverRefundSignatureMap.entries()) {
|
|
145
|
+
const leafData = receiverLeafDataMap.get(nodeId);
|
|
146
|
+
if (!leafData?.refundTx) {
|
|
147
|
+
throw new Error(`No refund tx for leaf ${nodeId}`);
|
|
148
|
+
}
|
|
149
|
+
const sighash = getSigHashFromTx(
|
|
150
|
+
leafData.refundTx,
|
|
151
|
+
0,
|
|
152
|
+
leafData.tx.getOutput(leafData.vout),
|
|
153
|
+
);
|
|
154
|
+
let verifyingPubkey: Uint8Array | undefined;
|
|
155
|
+
for (const signingResult of operatorSigningResults) {
|
|
156
|
+
if (signingResult.leafId === nodeId) {
|
|
157
|
+
verifyingPubkey = signingResult.verifyingKey;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
expect(verifyingPubkey).toBeDefined();
|
|
161
|
+
const taprootKey = computeTaprootKeyNoScript(
|
|
162
|
+
verifyingPubkey!.slice(1, 33),
|
|
163
|
+
);
|
|
164
|
+
const adaptorSig = applyAdaptorToSignature(
|
|
165
|
+
taprootKey.slice(1, 33),
|
|
166
|
+
sighash,
|
|
167
|
+
signature,
|
|
168
|
+
adaptorPrivateKey,
|
|
169
|
+
);
|
|
170
|
+
newReceiverRefundSignatureMap.set(nodeId, adaptorSig);
|
|
171
|
+
}
|
|
172
|
+
const senderTransferTweakKey =
|
|
173
|
+
await senderTransferService.sendTransferTweakKey(
|
|
174
|
+
senderTransfer,
|
|
175
|
+
senderLeavesToTransfer,
|
|
176
|
+
senderRefundSignatureMap,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const pendingTransfer =
|
|
180
|
+
await receiverTransferService.queryPendingTransfers();
|
|
181
|
+
expect(pendingTransfer.transfers.length).toBe(1);
|
|
182
|
+
const receiverPendingTransfer = pendingTransfer.transfers[0];
|
|
183
|
+
expect(receiverPendingTransfer!.id).toBe(senderTransferTweakKey.id);
|
|
184
|
+
|
|
185
|
+
const leafPrivKeyMap =
|
|
186
|
+
await receiverTransferService.verifyPendingTransfer(
|
|
187
|
+
receiverPendingTransfer!,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(leafPrivKeyMap.size).toBe(1);
|
|
191
|
+
expect(leafPrivKeyMap.get(senderRootNode.id)).toBeDefined();
|
|
192
|
+
const bytesEqual = equalBytes(
|
|
193
|
+
leafPrivKeyMap.get(senderRootNode.id)!,
|
|
194
|
+
senderNewLeafPubKey,
|
|
195
|
+
);
|
|
196
|
+
expect(bytesEqual).toBe(true);
|
|
197
|
+
expect(receiverPendingTransfer!.leaves[0]!.leaf).toBeDefined();
|
|
198
|
+
const finalLeafPubKey = await receiverWallet
|
|
199
|
+
.getSigner()
|
|
200
|
+
.generatePublicKey(sha256("2"));
|
|
201
|
+
const claimingNode: LeafKeyTweak = {
|
|
202
|
+
leaf: receiverPendingTransfer!.leaves[0]!.leaf!,
|
|
203
|
+
signingPubKey: senderNewLeafPubKey,
|
|
204
|
+
newSigningPubKey: finalLeafPubKey,
|
|
205
|
+
};
|
|
206
|
+
const leavesToClaim = [claimingNode];
|
|
207
|
+
await receiverTransferService.claimTransfer(
|
|
208
|
+
receiverPendingTransfer!,
|
|
209
|
+
leavesToClaim,
|
|
210
|
+
);
|
|
211
|
+
await receiverTransferService.sendTransferTweakKey(
|
|
212
|
+
receiverTransfer,
|
|
213
|
+
receiverLeavesToTransfer,
|
|
214
|
+
newReceiverRefundSignatureMap,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const sPendingTransfer =
|
|
218
|
+
await senderTransferService.queryPendingTransfers();
|
|
219
|
+
expect(sPendingTransfer.transfers.length).toBe(1);
|
|
220
|
+
const senderPendingTransfer = sPendingTransfer.transfers[0];
|
|
221
|
+
expect(senderPendingTransfer!.id).toBe(receiverTransfer.id);
|
|
222
|
+
|
|
223
|
+
const senderLeafPrivKeyMap =
|
|
224
|
+
await senderTransferService.verifyPendingTransfer(
|
|
225
|
+
senderPendingTransfer!,
|
|
226
|
+
);
|
|
227
|
+
expect(senderLeafPrivKeyMap.size).toBe(1);
|
|
228
|
+
expect(senderLeafPrivKeyMap.get(receiverRootNode.id)).toBeDefined();
|
|
229
|
+
const bytesEqual_1 = equalBytes(
|
|
230
|
+
senderLeafPrivKeyMap.get(receiverRootNode.id)!,
|
|
231
|
+
receiverNewLeafPubKey,
|
|
232
|
+
);
|
|
233
|
+
expect(bytesEqual_1).toBe(true);
|
|
234
|
+
expect(senderPendingTransfer!.leaves[0]!.leaf).toBeDefined();
|
|
235
|
+
|
|
236
|
+
const finalLeafPubKey_1 = await senderWallet
|
|
237
|
+
.getSigner()
|
|
238
|
+
.generatePublicKey(sha256("3"));
|
|
239
|
+
const claimingNode_1: LeafKeyTweak = {
|
|
240
|
+
leaf: senderPendingTransfer!.leaves[0]!.leaf!,
|
|
241
|
+
signingPubKey: receiverNewLeafPubKey,
|
|
242
|
+
newSigningPubKey: finalLeafPubKey_1,
|
|
243
|
+
};
|
|
244
|
+
const leavesToClaim_1 = [claimingNode_1];
|
|
245
|
+
await senderTransferService.claimTransfer(
|
|
246
|
+
senderPendingTransfer!,
|
|
247
|
+
leavesToClaim_1,
|
|
248
|
+
);
|
|
249
|
+
},
|
|
250
|
+
30000,
|
|
251
|
+
);
|
|
252
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
2
|
+
import { Address, OutScript, Transaction } from "@scure/btc-signer";
|
|
3
|
+
import { TreeNode } from "../proto/spark.js";
|
|
4
|
+
import { WalletConfigService } from "../services/config.js";
|
|
5
|
+
import { ConnectionManager } from "../services/connection.js";
|
|
6
|
+
import { DepositService } from "../services/deposit.js";
|
|
7
|
+
import {
|
|
8
|
+
ConfigOptions,
|
|
9
|
+
LOCAL_WALLET_CONFIG,
|
|
10
|
+
} from "../services/wallet-config.js";
|
|
11
|
+
import { getP2TRAddressFromPublicKey } from "../utils/bitcoin.js";
|
|
12
|
+
import { getNetwork, Network } from "../utils/network.js";
|
|
13
|
+
import { SparkWalletTesting } from "./utils/spark-testing-wallet.js";
|
|
14
|
+
import { BitcoinFaucet } from "./utils/test-faucet.js";
|
|
15
|
+
|
|
16
|
+
export function getTestWalletConfig() {
|
|
17
|
+
const identityPrivateKey = secp256k1.utils.randomPrivateKey();
|
|
18
|
+
return getTestWalletConfigWithIdentityKey(identityPrivateKey);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getTestWalletConfigWithIdentityKey(
|
|
22
|
+
identityPrivateKey: Uint8Array,
|
|
23
|
+
) {
|
|
24
|
+
return {
|
|
25
|
+
...LOCAL_WALLET_CONFIG,
|
|
26
|
+
identityPrivateKey,
|
|
27
|
+
} as ConfigOptions;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function createNewTree(
|
|
31
|
+
wallet: SparkWalletTesting,
|
|
32
|
+
pubKey: Uint8Array,
|
|
33
|
+
faucet: BitcoinFaucet,
|
|
34
|
+
amountSats: bigint = 100_000n,
|
|
35
|
+
): Promise<TreeNode> {
|
|
36
|
+
const faucetCoin = await faucet.fund();
|
|
37
|
+
|
|
38
|
+
const configService = new WalletConfigService(
|
|
39
|
+
{
|
|
40
|
+
network: "LOCAL",
|
|
41
|
+
},
|
|
42
|
+
wallet.getSigner(),
|
|
43
|
+
);
|
|
44
|
+
const connectionManager = new ConnectionManager(configService);
|
|
45
|
+
const depositService = new DepositService(configService, connectionManager);
|
|
46
|
+
|
|
47
|
+
const depositResp = await depositService.generateDepositAddress({
|
|
48
|
+
signingPubkey: pubKey,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!depositResp.depositAddress) {
|
|
52
|
+
throw new Error("deposit address not found");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const depositTx = new Transaction();
|
|
56
|
+
depositTx.addInput(faucetCoin!.outpoint);
|
|
57
|
+
|
|
58
|
+
// Add the main output
|
|
59
|
+
const addr = Address(getNetwork(Network.LOCAL)).decode(
|
|
60
|
+
depositResp.depositAddress.address,
|
|
61
|
+
);
|
|
62
|
+
const script = OutScript.encode(addr);
|
|
63
|
+
depositTx.addOutput({ script, amount: amountSats });
|
|
64
|
+
|
|
65
|
+
const treeResp = await depositService.createTreeRoot({
|
|
66
|
+
signingPubKey: pubKey,
|
|
67
|
+
verifyingKey: depositResp.depositAddress.verifyingKey,
|
|
68
|
+
depositTx,
|
|
69
|
+
vout: 0,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const signedDepositTx = await faucet.signFaucetCoin(
|
|
73
|
+
depositTx,
|
|
74
|
+
faucetCoin!.txout,
|
|
75
|
+
faucetCoin!.key,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
await faucet.broadcastTx(signedDepositTx.hex);
|
|
79
|
+
|
|
80
|
+
// Mine just 1 block instead of waiting for many confirmations
|
|
81
|
+
const randomKey = secp256k1.utils.randomPrivateKey();
|
|
82
|
+
const randomPubKey = secp256k1.getPublicKey(randomKey);
|
|
83
|
+
const randomAddress = getP2TRAddressFromPublicKey(
|
|
84
|
+
randomPubKey,
|
|
85
|
+
Network.LOCAL,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
await faucet.generateToAddress(1, randomAddress);
|
|
89
|
+
|
|
90
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
91
|
+
return treeResp.nodes[0]!;
|
|
92
|
+
}
|