@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.
Files changed (131) hide show
  1. package/dist/services/wallet-config.d.ts +1 -0
  2. package/dist/services/wallet-config.js +1 -0
  3. package/dist/services/wallet-config.js.map +1 -1
  4. package/dist/spark-sdk.d.ts +1 -1
  5. package/dist/spark-sdk.js +3 -3
  6. package/dist/spark-sdk.js.map +1 -1
  7. package/package.json +4 -3
  8. package/src/examples/example.js +247 -0
  9. package/src/examples/example.ts +207 -0
  10. package/src/graphql/client.ts +282 -0
  11. package/src/graphql/mutations/CompleteCoopExit.ts +19 -0
  12. package/src/graphql/mutations/CompleteLeavesSwap.ts +17 -0
  13. package/src/graphql/mutations/RequestCoopExit.ts +20 -0
  14. package/src/graphql/mutations/RequestLightningReceive.ts +26 -0
  15. package/src/graphql/mutations/RequestLightningSend.ts +17 -0
  16. package/src/graphql/mutations/RequestSwapLeaves.ts +24 -0
  17. package/src/graphql/objects/BitcoinNetwork.ts +22 -0
  18. package/src/graphql/objects/CompleteCoopExitInput.ts +41 -0
  19. package/src/graphql/objects/CompleteCoopExitOutput.ts +45 -0
  20. package/src/graphql/objects/CompleteLeavesSwapInput.ts +45 -0
  21. package/src/graphql/objects/CompleteLeavesSwapOutput.ts +45 -0
  22. package/src/graphql/objects/CompleteSeedReleaseInput.ts +41 -0
  23. package/src/graphql/objects/CompleteSeedReleaseOutput.ts +43 -0
  24. package/src/graphql/objects/Connection.ts +90 -0
  25. package/src/graphql/objects/CoopExitFeeEstimateInput.ts +41 -0
  26. package/src/graphql/objects/CoopExitFeeEstimateOutput.ts +52 -0
  27. package/src/graphql/objects/CoopExitRequest.ts +118 -0
  28. package/src/graphql/objects/CurrencyAmount.ts +74 -0
  29. package/src/graphql/objects/CurrencyUnit.ts +32 -0
  30. package/src/graphql/objects/Entity.ts +202 -0
  31. package/src/graphql/objects/GetChallengeInput.ts +37 -0
  32. package/src/graphql/objects/GetChallengeOutput.ts +43 -0
  33. package/src/graphql/objects/Invoice.ts +83 -0
  34. package/src/graphql/objects/Leaf.ts +59 -0
  35. package/src/graphql/objects/LeavesSwapFeeEstimateInput.ts +37 -0
  36. package/src/graphql/objects/LeavesSwapFeeEstimateOutput.ts +52 -0
  37. package/src/graphql/objects/LeavesSwapRequest.ts +192 -0
  38. package/src/graphql/objects/LightningReceiveFeeEstimateInput.ts +41 -0
  39. package/src/graphql/objects/LightningReceiveFeeEstimateOutput.ts +52 -0
  40. package/src/graphql/objects/LightningReceiveRequest.ts +147 -0
  41. package/src/graphql/objects/LightningReceiveRequestStatus.ts +34 -0
  42. package/src/graphql/objects/LightningSendFeeEstimateInput.ts +37 -0
  43. package/src/graphql/objects/LightningSendFeeEstimateOutput.ts +52 -0
  44. package/src/graphql/objects/LightningSendRequest.ts +134 -0
  45. package/src/graphql/objects/LightningSendRequestStatus.ts +28 -0
  46. package/src/graphql/objects/NotifyReceiverTransferInput.ts +41 -0
  47. package/src/graphql/objects/PageInfo.ts +58 -0
  48. package/src/graphql/objects/Provider.ts +41 -0
  49. package/src/graphql/objects/RequestCoopExitInput.ts +41 -0
  50. package/src/graphql/objects/RequestCoopExitOutput.ts +45 -0
  51. package/src/graphql/objects/RequestLeavesSwapInput.ts +55 -0
  52. package/src/graphql/objects/RequestLeavesSwapOutput.ts +45 -0
  53. package/src/graphql/objects/RequestLightningReceiveInput.ts +58 -0
  54. package/src/graphql/objects/RequestLightningReceiveOutput.ts +45 -0
  55. package/src/graphql/objects/RequestLightningSendInput.ts +41 -0
  56. package/src/graphql/objects/RequestLightningSendOutput.ts +45 -0
  57. package/src/graphql/objects/SparkCoopExitRequestStatus.ts +20 -0
  58. package/src/graphql/objects/SparkLeavesSwapRequestStatus.ts +20 -0
  59. package/src/graphql/objects/SparkTransferToLeavesConnection.ts +79 -0
  60. package/src/graphql/objects/SparkWalletUser.ts +86 -0
  61. package/src/graphql/objects/StartSeedReleaseInput.ts +37 -0
  62. package/src/graphql/objects/SwapLeaf.ts +53 -0
  63. package/src/graphql/objects/Transfer.ts +98 -0
  64. package/src/graphql/objects/UserLeafInput.ts +28 -0
  65. package/src/graphql/objects/VerifyChallengeInput.ts +51 -0
  66. package/src/graphql/objects/VerifyChallengeOutput.ts +43 -0
  67. package/src/graphql/objects/WalletUserIdentityPublicKeyInput.ts +37 -0
  68. package/src/graphql/objects/WalletUserIdentityPublicKeyOutput.ts +43 -0
  69. package/src/graphql/objects/index.ts +67 -0
  70. package/src/graphql/queries/CoopExitFeeEstimate.ts +18 -0
  71. package/src/graphql/queries/CurrentUser.ts +10 -0
  72. package/src/graphql/queries/LightningReceiveFeeEstimate.ts +18 -0
  73. package/src/graphql/queries/LightningSendFeeEstimate.ts +16 -0
  74. package/src/proto/common.ts +431 -0
  75. package/src/proto/google/protobuf/descriptor.ts +6625 -0
  76. package/src/proto/google/protobuf/duration.ts +197 -0
  77. package/src/proto/google/protobuf/empty.ts +83 -0
  78. package/src/proto/google/protobuf/timestamp.ts +226 -0
  79. package/src/proto/mock.ts +151 -0
  80. package/src/proto/spark.ts +12727 -0
  81. package/src/proto/spark_authn.ts +673 -0
  82. package/src/proto/validate/validate.ts +6047 -0
  83. package/src/services/config.ts +71 -0
  84. package/src/services/connection.ts +264 -0
  85. package/src/services/coop-exit.ts +190 -0
  86. package/src/services/deposit.ts +327 -0
  87. package/src/services/lightning.ts +341 -0
  88. package/src/services/lrc20.ts +42 -0
  89. package/src/services/token-transactions.ts +499 -0
  90. package/src/services/transfer.ts +1188 -0
  91. package/src/services/tree-creation.ts +618 -0
  92. package/src/services/wallet-config.ts +141 -0
  93. package/src/signer/signer.ts +531 -0
  94. package/src/spark-sdk.ts +1644 -0
  95. package/src/tests/adaptor-signature.test.ts +64 -0
  96. package/src/tests/bitcoin.test.ts +122 -0
  97. package/src/tests/coop-exit.test.ts +233 -0
  98. package/src/tests/deposit.test.ts +98 -0
  99. package/src/tests/keys.test.ts +82 -0
  100. package/src/tests/lightning.test.ts +307 -0
  101. package/src/tests/secret-sharing.test.ts +63 -0
  102. package/src/tests/swap.test.ts +252 -0
  103. package/src/tests/test-util.ts +92 -0
  104. package/src/tests/tokens.test.ts +47 -0
  105. package/src/tests/transfer.test.ts +371 -0
  106. package/src/tests/tree-creation.test.ts +56 -0
  107. package/src/tests/utils/spark-testing-wallet.ts +37 -0
  108. package/src/tests/utils/test-faucet.ts +257 -0
  109. package/src/types/grpc.ts +8 -0
  110. package/src/types/index.ts +3 -0
  111. package/src/utils/adaptor-signature.ts +189 -0
  112. package/src/utils/bitcoin.ts +138 -0
  113. package/src/utils/crypto.ts +14 -0
  114. package/src/utils/index.ts +12 -0
  115. package/src/utils/keys.ts +92 -0
  116. package/src/utils/mempool.ts +42 -0
  117. package/src/utils/network.ts +70 -0
  118. package/src/utils/proof.ts +17 -0
  119. package/src/utils/response-validation.ts +26 -0
  120. package/src/utils/secret-sharing.ts +263 -0
  121. package/src/utils/signing.ts +96 -0
  122. package/src/utils/token-hashing.ts +163 -0
  123. package/src/utils/token-keyshares.ts +31 -0
  124. package/src/utils/token-transactions.ts +71 -0
  125. package/src/utils/transaction.ts +45 -0
  126. package/src/utils/wasm-wrapper.ts +57 -0
  127. package/src/utils/wasm.ts +154 -0
  128. package/src/wasm/spark_bindings.d.ts +208 -0
  129. package/src/wasm/spark_bindings.js +1161 -0
  130. package/src/wasm/spark_bindings_bg.wasm +0 -0
  131. package/src/wasm/spark_bindings_bg.wasm.d.ts +136 -0
@@ -0,0 +1,257 @@
1
+ import { bytesToHex, hexToBytes } from "@noble/curves/abstract/utils";
2
+ import { schnorr, secp256k1 } from "@noble/curves/secp256k1";
3
+ import { Address, OutScript, SigHash, Transaction } from "@scure/btc-signer";
4
+ import { TransactionInput, TransactionOutput } from "@scure/btc-signer/psbt";
5
+ import { taprootTweakPrivKey } from "@scure/btc-signer/utils";
6
+ import {
7
+ getP2TRAddressFromPublicKey,
8
+ getP2TRScriptFromPublicKey,
9
+ } from "../../utils/bitcoin.js";
10
+ import { getNetwork, Network } from "../../utils/network.js";
11
+ import { SparkWallet } from "../../spark-sdk.js";
12
+ import { sha256 } from "@noble/hashes/sha256";
13
+ import * as btc from "@scure/btc-signer";
14
+ import { ripemd160 } from "@noble/hashes/ripemd160";
15
+
16
+ export type FaucetCoin = {
17
+ key: Uint8Array;
18
+ outpoint: TransactionInput;
19
+ txout: TransactionOutput;
20
+ };
21
+
22
+ const COIN_AMOUNT = 10_000_000n;
23
+
24
+ export class BitcoinFaucet {
25
+ private coins: FaucetCoin[] = [];
26
+ private static instance: BitcoinFaucet | null = null;
27
+
28
+ constructor(
29
+ private url: string,
30
+ private username: string,
31
+ private password: string,
32
+ ) {
33
+ if (BitcoinFaucet.instance) {
34
+ return BitcoinFaucet.instance;
35
+ }
36
+
37
+ BitcoinFaucet.instance = this;
38
+ }
39
+
40
+ async fund() {
41
+ // If no coins available, refill the faucet
42
+ if (this.coins.length === 0) {
43
+ await this.refill();
44
+ }
45
+
46
+ // Take the first coin from the faucet
47
+ const coin = this.coins[0];
48
+ // Remove the used coin from the array
49
+ this.coins = this.coins.slice(1);
50
+
51
+ return coin;
52
+ }
53
+
54
+ async refill() {
55
+ // Generate key for initial block reward
56
+ const key = secp256k1.utils.randomPrivateKey();
57
+ const pubKey = secp256k1.getPublicKey(key);
58
+ const address = getP2TRAddressFromPublicKey(pubKey, Network.LOCAL);
59
+
60
+ // Mine a block to this address
61
+ const blockHash = await this.generateToAddress(1, address);
62
+
63
+ // Get block and funding transaction
64
+ const block = await this.getBlock(blockHash[0]);
65
+ const fundingTx = Transaction.fromRaw(hexToBytes(block.tx[0].hex), {
66
+ allowUnknownOutputs: true,
67
+ });
68
+
69
+ // Mine 100 blocks to make funds spendable
70
+ const randomKey = secp256k1.utils.randomPrivateKey();
71
+ const randomPubKey = secp256k1.getPublicKey(randomKey);
72
+ const randomAddress = getP2TRAddressFromPublicKey(
73
+ randomPubKey,
74
+ Network.LOCAL,
75
+ );
76
+ await this.generateToAddress(100, randomAddress);
77
+
78
+ const fundingTxId = block.tx[0].txid;
79
+ const fundingOutpoint: TransactionInput = {
80
+ txid: fundingTxId,
81
+ index: 0,
82
+ };
83
+
84
+ const splitTx = new Transaction();
85
+ splitTx.addInput(fundingOutpoint);
86
+ let initialValue = fundingTx.getOutput(0)!.amount!;
87
+ const coinAmount = 10_000_000n;
88
+ const coinKeys: Uint8Array[] = [];
89
+
90
+ while (initialValue > coinAmount + 100_000n) {
91
+ const coinKey = secp256k1.utils.randomPrivateKey();
92
+ const coinPubKey = secp256k1.getPublicKey(coinKey);
93
+ coinKeys.push(coinKey);
94
+
95
+ const script = getP2TRScriptFromPublicKey(coinPubKey, Network.LOCAL);
96
+ splitTx.addOutput({
97
+ script,
98
+ amount: coinAmount,
99
+ });
100
+ initialValue -= coinAmount;
101
+ }
102
+ // Sign and broadcast
103
+ const signedSplitTx = await this.signFaucetCoin(
104
+ splitTx,
105
+ fundingTx.getOutput(0)!,
106
+ key,
107
+ );
108
+
109
+ await this.broadcastTx(bytesToHex(signedSplitTx.extract()));
110
+
111
+ // Create faucet coins
112
+ const splitTxId = signedSplitTx.id;
113
+ for (let i = 0; i < signedSplitTx.outputsLength; i++) {
114
+ this.coins.push({
115
+ // @ts-ignore - It's a test file
116
+ key: coinKeys[i],
117
+ outpoint: {
118
+ txid: hexToBytes(splitTxId),
119
+ index: i,
120
+ },
121
+ txout: signedSplitTx.getOutput(i)!,
122
+ });
123
+ }
124
+ }
125
+ async sendFaucetCoinToP2WPKHAddress(pubKey: Uint8Array) {
126
+ const sendToPubKeyTx = new Transaction();
127
+
128
+ // For P2WPKH, we need to hash the public key
129
+
130
+ // Create a P2WPKH address
131
+ const p2wpkhAddress = btc.p2wpkh(pubKey, getNetwork(Network.LOCAL)).address;
132
+ if (!p2wpkhAddress) {
133
+ throw new Error("Invalid P2WPKH address");
134
+ }
135
+
136
+ // Get the coin to spend
137
+ const coinToSend = await this.fund();
138
+ if (!coinToSend) {
139
+ throw new Error("No coins available");
140
+ }
141
+
142
+ // Add the input
143
+ sendToPubKeyTx.addInput(coinToSend.outpoint);
144
+
145
+ // Add the output using the address directly
146
+ sendToPubKeyTx.addOutputAddress(
147
+ p2wpkhAddress,
148
+ 100_000n,
149
+ getNetwork(Network.LOCAL),
150
+ );
151
+
152
+ // Sign the transaction and get the signed result
153
+ const signedTx = await this.signFaucetCoin(
154
+ sendToPubKeyTx,
155
+ coinToSend.txout,
156
+ coinToSend.key,
157
+ );
158
+
159
+ // Broadcast the signed transaction
160
+ await this.broadcastTx(bytesToHex(signedTx.extract()));
161
+ }
162
+
163
+ async signFaucetCoin(
164
+ unsignedTx: Transaction,
165
+ fundingTxOut: TransactionOutput,
166
+ key: Uint8Array,
167
+ ): Promise<Transaction> {
168
+ const pubKey = secp256k1.getPublicKey(key);
169
+ const internalKey = pubKey.slice(1); // Remove the 0x02/0x03 prefix
170
+
171
+ const script = getP2TRScriptFromPublicKey(pubKey, Network.LOCAL);
172
+
173
+ unsignedTx.updateInput(0, {
174
+ tapInternalKey: internalKey,
175
+ witnessUtxo: {
176
+ script,
177
+ amount: fundingTxOut.amount!,
178
+ },
179
+ });
180
+
181
+ const sighash = unsignedTx.preimageWitnessV1(
182
+ 0,
183
+ new Array(unsignedTx.inputsLength).fill(script),
184
+ SigHash.DEFAULT,
185
+ new Array(unsignedTx.inputsLength).fill(fundingTxOut.amount!),
186
+ );
187
+
188
+ const merkleRoot = new Uint8Array();
189
+ const tweakedKey = taprootTweakPrivKey(key, merkleRoot);
190
+ if (!tweakedKey)
191
+ throw new Error("Invalid private key for taproot tweaking");
192
+
193
+ const signature = schnorr.sign(sighash, tweakedKey);
194
+
195
+ unsignedTx.updateInput(0, {
196
+ tapKeySig: signature,
197
+ });
198
+
199
+ unsignedTx.finalize();
200
+
201
+ return unsignedTx;
202
+ }
203
+
204
+ // MineBlocks mines the specified number of blocks to a random address
205
+ // and returns the block hashes.
206
+ async mineBlocks(numBlocks: number) {
207
+ // Mine 100 blocks to make funds spendable
208
+ const randomKey = secp256k1.utils.randomPrivateKey();
209
+ const randomPubKey = secp256k1.getPublicKey(randomKey);
210
+ const randomAddress = getP2TRAddressFromPublicKey(
211
+ randomPubKey,
212
+ Network.LOCAL,
213
+ );
214
+ return await this.generateToAddress(100, randomAddress);
215
+ }
216
+
217
+ private async call(method: string, params: any[]) {
218
+ try {
219
+ const response = await fetch(this.url, {
220
+ method: "POST",
221
+ headers: {
222
+ "Content-Type": "application/json",
223
+ Authorization: "Basic " + btoa(`${this.username}:${this.password}`),
224
+ },
225
+ body: JSON.stringify({
226
+ jsonrpc: "1.0",
227
+ id: "spark-js",
228
+ method,
229
+ params,
230
+ }),
231
+ });
232
+
233
+ const data = await response.json();
234
+ if (data.error) {
235
+ console.error(`RPC Error for method ${method}:`, data.error);
236
+ throw new Error(`Bitcoin RPC error: ${data.error.message}`);
237
+ }
238
+
239
+ return data.result;
240
+ } catch (error) {
241
+ console.error("Error calling Bitcoin RPC:", error);
242
+ throw error;
243
+ }
244
+ }
245
+
246
+ async generateToAddress(numBlocks: number, address: string) {
247
+ return await this.call("generatetoaddress", [numBlocks, address]);
248
+ }
249
+
250
+ async getBlock(blockHash: string) {
251
+ return await this.call("getblock", [blockHash, 2]);
252
+ }
253
+
254
+ async broadcastTx(txHex: string) {
255
+ return await this.call("sendrawtransaction", [txHex, 0]);
256
+ }
257
+ }
@@ -0,0 +1,8 @@
1
+ import { CallOptions } from "nice-grpc";
2
+
3
+ export interface RetryOptions {
4
+ retry?: boolean;
5
+ retryMaxAttempts?: number;
6
+ }
7
+
8
+ export type SparkCallOptions = CallOptions & RetryOptions;
@@ -0,0 +1,3 @@
1
+ export * from "../graphql/objects/index.js";
2
+ export * as CommonProto from "../proto/common.js";
3
+ export * as SparkProto from "../proto/spark.js";
@@ -0,0 +1,189 @@
1
+ import { mod } from "@noble/curves/abstract/modular";
2
+ import { bytesToNumberBE, numberToBytesBE } from "@noble/curves/abstract/utils";
3
+ import { schnorr, secp256k1 } from "@noble/curves/secp256k1";
4
+
5
+ export function generateSignatureFromExistingAdaptor(
6
+ signature: Uint8Array,
7
+ adaptorPrivateKeyBytes: Uint8Array,
8
+ ): Uint8Array {
9
+ const { r, s } = parseSignature(signature);
10
+
11
+ const sBigInt = bytesToNumberBE(s);
12
+ const tBigInt = bytesToNumberBE(adaptorPrivateKeyBytes);
13
+
14
+ const newS = mod(sBigInt - tBigInt, secp256k1.CURVE.n);
15
+
16
+ const newSignature = new Uint8Array([...r, ...numberToBytesBE(newS, 32)]);
17
+
18
+ return newSignature;
19
+ }
20
+
21
+ export function generateAdaptorFromSignature(signature: Uint8Array): {
22
+ adaptorSignature: Uint8Array;
23
+ adaptorPrivateKey: Uint8Array;
24
+ } {
25
+ const adaptorPrivateKey = secp256k1.utils.randomPrivateKey();
26
+
27
+ const { r, s } = parseSignature(signature);
28
+
29
+ const sBigInt = bytesToNumberBE(s);
30
+ const tBigInt = bytesToNumberBE(adaptorPrivateKey);
31
+
32
+ // Calculate s - adaptorPrivateKey
33
+ const newS = mod(sBigInt - tBigInt, secp256k1.CURVE.n);
34
+
35
+ const newSignature = new Uint8Array([...r, ...numberToBytesBE(newS, 32)]);
36
+
37
+ return {
38
+ adaptorSignature: newSignature,
39
+ adaptorPrivateKey: adaptorPrivateKey,
40
+ };
41
+ }
42
+
43
+ export function validateOutboundAdaptorSignature(
44
+ pubkey: Uint8Array,
45
+ hash: Uint8Array,
46
+ signature: Uint8Array,
47
+ adaptorPubkey: Uint8Array,
48
+ ): boolean {
49
+ return schnorrVerifyWithAdaptor(
50
+ signature,
51
+ hash,
52
+ pubkey,
53
+ adaptorPubkey,
54
+ false,
55
+ );
56
+ }
57
+
58
+ export function applyAdaptorToSignature(
59
+ pubkey: Uint8Array,
60
+ hash: Uint8Array,
61
+ signature: Uint8Array,
62
+ adaptorPrivateKeyBytes: Uint8Array,
63
+ ): Uint8Array {
64
+ // Parse the signature
65
+ const { r, s } = parseSignature(signature);
66
+
67
+ // Convert values to bigints
68
+ const sBigInt = bytesToNumberBE(s);
69
+ const adaptorPrivateKey = bytesToNumberBE(adaptorPrivateKeyBytes);
70
+
71
+ // Try adding adaptor to s first
72
+ const newS = mod(sBigInt + adaptorPrivateKey, secp256k1.CURVE.n);
73
+ const newSig = new Uint8Array([...r, ...numberToBytesBE(newS, 32)]);
74
+
75
+ if (schnorr.verify(newSig, hash, pubkey)) {
76
+ return newSig;
77
+ }
78
+
79
+ // If adding didn't work, try subtracting
80
+ const altS = mod(sBigInt - adaptorPrivateKey, secp256k1.CURVE.n);
81
+ const altSig = new Uint8Array([...r, ...numberToBytesBE(altS, 32)]);
82
+
83
+ if (schnorr.verify(altSig, hash, pubkey)) {
84
+ return altSig;
85
+ }
86
+
87
+ throw new Error("Cannot apply adaptor to signature");
88
+ }
89
+
90
+ // Step 1: P = lift_x(int(pk))
91
+ // Step 2: r = int(sig[0:32])
92
+ // Step 3: s = int(sig[32:64])
93
+ // Step 4: e = int(tagged_hash("BIP0340/challenge", bytes(r) || bytes(P) || m)) mod n
94
+ // Step 5: R = sG - eP
95
+ // Step 6: R = R + T
96
+ function schnorrVerifyWithAdaptor(
97
+ signature: Uint8Array,
98
+ hash: Uint8Array,
99
+ pubKeyBytes: Uint8Array,
100
+ adaptorPubkey: Uint8Array,
101
+ inbound: boolean,
102
+ ): boolean {
103
+ // Step 1: Verify message length
104
+ if (hash.length !== 32) {
105
+ throw new Error(`wrong size for message (got ${hash.length}, want 32)`);
106
+ }
107
+
108
+ // Step 2: Lift x coordinate to curve point
109
+ const pubKey = schnorr.utils.lift_x(bytesToNumberBE(pubKeyBytes));
110
+ pubKey.assertValidity();
111
+
112
+ // Parse signature
113
+ // Step 3 and 4 is handled by parseSignature
114
+ const { r, s } = parseSignature(signature);
115
+
116
+ // Step 5: Compute challenge
117
+ const commitmenet = schnorr.utils.taggedHash(
118
+ "BIP0340/challenge",
119
+ r,
120
+ pubKey.toRawBytes().slice(1),
121
+ hash,
122
+ );
123
+ if (commitmenet.length > 32) {
124
+ throw new Error("hash of (r || P || m) too big");
125
+ }
126
+
127
+ const e = mod(bytesToNumberBE(commitmenet), secp256k1.CURVE.n);
128
+ const negE = mod(-e, secp256k1.CURVE.n); // Negate e before multiplication
129
+
130
+ // Step 6: Calculate R = sG - eP
131
+ const R = secp256k1.ProjectivePoint.BASE.multiplyAndAddUnsafe(
132
+ pubKey,
133
+ bytesToNumberBE(s),
134
+ negE,
135
+ );
136
+ if (!R) {
137
+ throw new Error("R is undefined");
138
+ }
139
+
140
+ R.assertValidity();
141
+
142
+ // Step 6.5: Add adaptor public key T to R
143
+ const adaptorPoint = secp256k1.ProjectivePoint.fromHex(adaptorPubkey);
144
+ const newR = R.add(adaptorPoint);
145
+
146
+ // Step 7: Check for point at infinity (if not inbound)
147
+ if (!inbound && newR.equals(secp256k1.ProjectivePoint.ZERO)) {
148
+ throw new Error("calculated R point is the point at infinity");
149
+ }
150
+
151
+ // Step 8: Check if R.y is odd
152
+ newR.assertValidity();
153
+ if (!newR.hasEvenY()) {
154
+ throw new Error("calculated R y-value is odd");
155
+ }
156
+
157
+ // Step 9: Check if R.x == r
158
+ const rNum = bytesToNumberBE(r);
159
+ if (newR.toAffine().x !== rNum) {
160
+ throw new Error("calculated R point was not given R");
161
+ }
162
+
163
+ return true;
164
+ }
165
+
166
+ function parseSignature(signature: Uint8Array): {
167
+ r: Uint8Array;
168
+ s: Uint8Array;
169
+ } {
170
+ if (signature.length < 64) {
171
+ throw new Error(`malformed signature: too short: ${signature.length} < 64`);
172
+ }
173
+ if (signature.length > 64) {
174
+ throw new Error(`malformed signature: too long: ${signature.length} > 64`);
175
+ }
176
+
177
+ const r = signature.slice(0, 32);
178
+ const s = signature.slice(32, 64);
179
+
180
+ if (bytesToNumberBE(r) >= secp256k1.CURVE.Fp.ORDER) {
181
+ throw new Error(`invalid signature: r >= field prime`);
182
+ }
183
+
184
+ if (bytesToNumberBE(s) >= secp256k1.CURVE.n) {
185
+ throw new Error(`invalid signature: s >= group order`);
186
+ }
187
+
188
+ return { r, s };
189
+ }
@@ -0,0 +1,138 @@
1
+ import {
2
+ bytesToHex,
3
+ bytesToNumberBE,
4
+ hexToBytes,
5
+ } from "@noble/curves/abstract/utils";
6
+ import { schnorr, secp256k1 } from "@noble/curves/secp256k1";
7
+
8
+ import * as btc from "@scure/btc-signer";
9
+ import { TransactionOutput } from "@scure/btc-signer/psbt";
10
+ import { sha256 } from "@scure/btc-signer/utils";
11
+ import { getNetwork, Network } from "./network.js";
12
+
13
+ // const t = tapTweak(pubKey, h); // t = int_from_bytes(tagged_hash("TapTweak", pubkey + h)
14
+ // const P = u.lift_x(u.bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey))
15
+ // const Q = P.add(Point.fromPrivateKey(t)); // Q = point_add(P, point_mul(G, t))
16
+ export function computeTaprootKeyNoScript(pubkey: Uint8Array): Uint8Array {
17
+ if (pubkey.length !== 32) {
18
+ throw new Error("Public key must be 32 bytes");
19
+ }
20
+
21
+ const taggedHash = schnorr.utils.taggedHash("TapTweak", pubkey);
22
+ const tweak = bytesToNumberBE(taggedHash);
23
+
24
+ // Get the original point
25
+ const P = schnorr.utils.lift_x(schnorr.utils.bytesToNumberBE(pubkey));
26
+
27
+ // Add the tweak times the generator point
28
+ const Q = P.add(secp256k1.ProjectivePoint.fromPrivateKey(tweak));
29
+
30
+ return Q.toRawBytes();
31
+ }
32
+
33
+ export function getP2TRScriptFromPublicKey(
34
+ pubKey: Uint8Array,
35
+ network: Network,
36
+ ): Uint8Array {
37
+ if (pubKey.length !== 33) {
38
+ throw new Error("Public key must be 33 bytes");
39
+ }
40
+
41
+ const internalKey = secp256k1.ProjectivePoint.fromHex(pubKey);
42
+ const script = btc.p2tr(
43
+ internalKey.toRawBytes().slice(1, 33),
44
+ undefined,
45
+ getNetwork(network),
46
+ ).script;
47
+ if (!script) {
48
+ throw new Error("Failed to get P2TR address");
49
+ }
50
+ return script;
51
+ }
52
+
53
+ export function getP2TRAddressFromPublicKey(
54
+ pubKey: Uint8Array,
55
+ network: Network,
56
+ ): string {
57
+ if (pubKey.length !== 33) {
58
+ throw new Error("Public key must be 33 bytes");
59
+ }
60
+
61
+ const internalKey = secp256k1.ProjectivePoint.fromHex(pubKey);
62
+ const address = btc.p2tr(
63
+ internalKey.toRawBytes().slice(1, 33),
64
+ undefined,
65
+ getNetwork(network),
66
+ ).address;
67
+ if (!address) {
68
+ throw new Error("Failed to get P2TR address");
69
+ }
70
+ return address;
71
+ }
72
+
73
+ export function getP2TRAddressFromPkScript(
74
+ pkScript: Uint8Array,
75
+ network: Network,
76
+ ): string {
77
+ if (pkScript.length !== 34 || pkScript[0] !== 0x51 || pkScript[1] !== 0x20) {
78
+ throw new Error("Invalid pkscript");
79
+ }
80
+
81
+ const parsedScript = btc.OutScript.decode(pkScript);
82
+
83
+ return btc.Address(getNetwork(network)).encode(parsedScript);
84
+ }
85
+
86
+ export function getTxFromRawTxHex(rawTxHex: string): btc.Transaction {
87
+ const txBytes = hexToBytes(rawTxHex);
88
+ const tx = btc.Transaction.fromRaw(txBytes, {
89
+ allowUnknownOutputs: true,
90
+ });
91
+
92
+ if (!tx) {
93
+ throw new Error("Failed to parse transaction");
94
+ }
95
+ return tx;
96
+ }
97
+
98
+ export function getTxFromRawTxBytes(rawTxBytes: Uint8Array): btc.Transaction {
99
+ const tx = btc.Transaction.fromRaw(rawTxBytes, {
100
+ allowUnknownOutputs: true,
101
+ });
102
+ if (!tx) {
103
+ throw new Error("Failed to parse transaction");
104
+ }
105
+ return tx;
106
+ }
107
+
108
+ export function getSigHashFromTx(
109
+ tx: btc.Transaction,
110
+ inputIndex: number,
111
+ prevOutput: TransactionOutput,
112
+ ): Uint8Array {
113
+ // For Taproot, we use preimageWitnessV1 with SIGHASH_DEFAULT (0x00)
114
+ const prevScript = prevOutput.script;
115
+ if (!prevScript) {
116
+ throw new Error("No script found in prevOutput");
117
+ }
118
+
119
+ const amount = prevOutput.amount;
120
+ if (!amount) {
121
+ throw new Error("No amount found in prevOutput");
122
+ }
123
+
124
+ return tx.preimageWitnessV1(
125
+ inputIndex,
126
+ new Array(tx.inputsLength).fill(prevScript),
127
+ btc.SigHash.DEFAULT,
128
+ new Array(tx.inputsLength).fill(amount),
129
+ );
130
+ }
131
+
132
+ export function getTxId(tx: btc.Transaction): string {
133
+ return bytesToHex(sha256(sha256(tx.unsignedTx)).reverse());
134
+ }
135
+
136
+ export function getTxIdNoReverse(tx: btc.Transaction): string {
137
+ return bytesToHex(sha256(sha256(tx.unsignedTx)));
138
+ }
@@ -0,0 +1,14 @@
1
+ import crypto from "crypto";
2
+
3
+ export const getCrypto = (): Crypto => {
4
+ // Browser environment
5
+ if (typeof window !== "undefined" && window.crypto) {
6
+ return window.crypto;
7
+ }
8
+ // Node.js environment
9
+ if (typeof global !== "undefined" && global.crypto) {
10
+ return global.crypto;
11
+ }
12
+ // Node.js environment without global.crypto (older versions)
13
+ return crypto as Crypto;
14
+ };
@@ -0,0 +1,12 @@
1
+ export * from "./adaptor-signature.js";
2
+ export * from "./bitcoin.js";
3
+ export * from "./keys.js";
4
+ export * from "./mempool.js";
5
+ export * from "./network.js";
6
+ export * from "./proof.js";
7
+ export * from "./response-validation.js";
8
+ export * from "./secret-sharing.js";
9
+ export * from "./signing.js";
10
+ export * from "./transaction.js";
11
+ export * from "./wasm-wrapper.js";
12
+ export * from "./wasm.js";