@buildonspark/spark-sdk 0.0.15 → 0.0.17
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/config.d.ts +1 -0
- package/dist/services/config.js +4 -1
- package/dist/services/config.js.map +1 -1
- package/dist/services/wallet-config.d.ts +2 -0
- package/dist/services/wallet-config.js +2 -0
- package/dist/services/wallet-config.js.map +1 -1
- package/dist/signer/signer.js +4 -3
- package/dist/signer/signer.js.map +1 -1
- package/dist/spark-sdk.d.ts +4 -4
- package/dist/spark-sdk.js +26 -10
- 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 +75 -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 +143 -0
- package/src/signer/signer.ts +532 -0
- package/src/spark-sdk.ts +1665 -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,92 @@
|
|
|
1
|
+
import { numberToBytesBE } from "@noble/curves/abstract/utils";
|
|
2
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
3
|
+
import { HDKey } from "@scure/bip32";
|
|
4
|
+
|
|
5
|
+
export function addPublicKeys(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
6
|
+
if (a.length !== 33 || b.length !== 33) {
|
|
7
|
+
throw new Error("Public keys must be 33 bytes");
|
|
8
|
+
}
|
|
9
|
+
const pubkeyA = secp256k1.ProjectivePoint.fromHex(a);
|
|
10
|
+
const pubkeyB = secp256k1.ProjectivePoint.fromHex(b);
|
|
11
|
+
return pubkeyA.add(pubkeyB).toRawBytes(true);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function applyAdditiveTweakToPublicKey(
|
|
15
|
+
pubkey: Uint8Array,
|
|
16
|
+
tweak: Uint8Array,
|
|
17
|
+
) {
|
|
18
|
+
if (pubkey.length !== 33) {
|
|
19
|
+
throw new Error("Public key must be 33 bytes");
|
|
20
|
+
}
|
|
21
|
+
if (tweak.length !== 32) {
|
|
22
|
+
throw new Error("Tweak must be 32 bytes");
|
|
23
|
+
}
|
|
24
|
+
const pubkeyPoint = secp256k1.ProjectivePoint.fromHex(pubkey);
|
|
25
|
+
|
|
26
|
+
const privTweek = secp256k1.utils.normPrivateKeyToScalar(tweak);
|
|
27
|
+
const pubTweek = secp256k1.getPublicKey(privTweek, true);
|
|
28
|
+
const tweekPoint = secp256k1.ProjectivePoint.fromHex(pubTweek);
|
|
29
|
+
|
|
30
|
+
return pubkeyPoint.add(tweekPoint).toRawBytes(true);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function subtractPublicKeys(a: Uint8Array, b: Uint8Array) {
|
|
34
|
+
if (a.length !== 33 || b.length !== 33) {
|
|
35
|
+
throw new Error("Public keys must be 33 bytes");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const pubkeyA = secp256k1.ProjectivePoint.fromHex(a);
|
|
39
|
+
const pubkeyB = secp256k1.ProjectivePoint.fromHex(b);
|
|
40
|
+
return pubkeyA.subtract(pubkeyB).toRawBytes(true);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function addPrivateKeys(a: Uint8Array, b: Uint8Array) {
|
|
44
|
+
if (a.length !== 32 || b.length !== 32) {
|
|
45
|
+
throw new Error("Private keys must be 32 bytes");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Convert private keys to scalars (big integers)
|
|
49
|
+
const privA = secp256k1.utils.normPrivateKeyToScalar(a);
|
|
50
|
+
const privB = secp256k1.utils.normPrivateKeyToScalar(b);
|
|
51
|
+
|
|
52
|
+
// Add the scalars and reduce modulo the curve order
|
|
53
|
+
const sum = (privA + privB) % secp256k1.CURVE.n;
|
|
54
|
+
|
|
55
|
+
// Convert back to bytes
|
|
56
|
+
return numberToBytesBE(sum, 32);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function subtractPrivateKeys(a: Uint8Array, b: Uint8Array) {
|
|
60
|
+
if (a.length !== 32 || b.length !== 32) {
|
|
61
|
+
throw new Error("Private keys must be 32 bytes");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const privA = secp256k1.utils.normPrivateKeyToScalar(a);
|
|
65
|
+
const privB = secp256k1.utils.normPrivateKeyToScalar(b);
|
|
66
|
+
const sum = (secp256k1.CURVE.n - privB + privA) % secp256k1.CURVE.n;
|
|
67
|
+
|
|
68
|
+
return numberToBytesBE(sum, 32);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function sumOfPrivateKeys(keys: Uint8Array[]) {
|
|
72
|
+
return keys.reduce((sum, key) => {
|
|
73
|
+
if (key.length !== 32) {
|
|
74
|
+
throw new Error("Private keys must be 32 bytes");
|
|
75
|
+
}
|
|
76
|
+
return addPrivateKeys(sum, key);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function lastKeyWithTarget(target: Uint8Array, keys: Uint8Array[]) {
|
|
81
|
+
if (target.length !== 32) {
|
|
82
|
+
throw new Error("Target must be 32 bytes");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const sum = sumOfPrivateKeys(keys);
|
|
86
|
+
return subtractPrivateKeys(target, sum);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getMasterHDKeyFromSeed(seed: Uint8Array): HDKey {
|
|
90
|
+
// TODO: This needs to be moved back to the signer
|
|
91
|
+
return HDKey.fromMasterSeed(seed);
|
|
92
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BitcoinNetwork } from "../types/index.js";
|
|
2
|
+
import { getNetworkFromAddress } from "./network.js";
|
|
3
|
+
|
|
4
|
+
export async function getLatestDepositTxId(
|
|
5
|
+
address: string,
|
|
6
|
+
): Promise<string | null> {
|
|
7
|
+
const network = getNetworkFromAddress(address);
|
|
8
|
+
const baseUrl =
|
|
9
|
+
network === BitcoinNetwork.REGTEST
|
|
10
|
+
? "https://regtest-mempool.dev.dev.sparkinfra.net/api"
|
|
11
|
+
: "https://mempool.space/docs/api";
|
|
12
|
+
const auth = btoa("spark-sdk:mCMk1JqlBNtetUNy");
|
|
13
|
+
|
|
14
|
+
const headers: Record<string, string> = {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (network === BitcoinNetwork.REGTEST) {
|
|
19
|
+
headers["Authorization"] = `Basic ${auth}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const response = await fetch(`${baseUrl}/address/${address}/txs`, {
|
|
23
|
+
headers,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const addressTxs = await response.json();
|
|
27
|
+
|
|
28
|
+
if (addressTxs && addressTxs.length > 0) {
|
|
29
|
+
const latestTx = addressTxs[0];
|
|
30
|
+
|
|
31
|
+
const outputIndex: number = latestTx.vout.findIndex(
|
|
32
|
+
(output: any) => output.scriptpubkey_address === address,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (outputIndex === -1) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return latestTx.txid;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { NetworkType as Lrc20NetworkType } from "@buildonspark/lrc20-sdk";
|
|
2
|
+
import * as btc from "@scure/btc-signer";
|
|
3
|
+
import * as bitcoin from "bitcoinjs-lib";
|
|
4
|
+
import { Network as NetworkProto } from "../proto/spark.js";
|
|
5
|
+
import { BitcoinNetwork } from "../types/index.js";
|
|
6
|
+
export enum Network {
|
|
7
|
+
MAINNET,
|
|
8
|
+
TESTNET,
|
|
9
|
+
SIGNET,
|
|
10
|
+
REGTEST,
|
|
11
|
+
LOCAL,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type NetworkType = keyof typeof Network;
|
|
15
|
+
|
|
16
|
+
export const NetworkToProto: Record<Network, NetworkProto> = {
|
|
17
|
+
[Network.MAINNET]: NetworkProto.MAINNET,
|
|
18
|
+
[Network.TESTNET]: NetworkProto.TESTNET,
|
|
19
|
+
[Network.SIGNET]: NetworkProto.SIGNET,
|
|
20
|
+
[Network.REGTEST]: NetworkProto.REGTEST,
|
|
21
|
+
[Network.LOCAL]: NetworkProto.REGTEST,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const NetworkConfig: Record<Network, typeof btc.NETWORK> = {
|
|
25
|
+
[Network.MAINNET]: btc.NETWORK,
|
|
26
|
+
[Network.TESTNET]: btc.TEST_NETWORK,
|
|
27
|
+
[Network.SIGNET]: btc.TEST_NETWORK,
|
|
28
|
+
[Network.REGTEST]: { ...btc.TEST_NETWORK, bech32: "bcrt" },
|
|
29
|
+
[Network.LOCAL]: { ...btc.TEST_NETWORK, bech32: "bcrt" },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const getNetwork = (network: Network): typeof btc.NETWORK =>
|
|
33
|
+
NetworkConfig[network];
|
|
34
|
+
|
|
35
|
+
export const LRC_WALLET_NETWORK = Object.freeze({
|
|
36
|
+
[Network.MAINNET]: bitcoin.networks.bitcoin,
|
|
37
|
+
[Network.TESTNET]: bitcoin.networks.testnet,
|
|
38
|
+
[Network.SIGNET]: bitcoin.networks.testnet,
|
|
39
|
+
[Network.REGTEST]: bitcoin.networks.regtest,
|
|
40
|
+
[Network.LOCAL]: bitcoin.networks.regtest,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const LRC_WALLET_NETWORK_TYPE = Object.freeze({
|
|
44
|
+
[Network.MAINNET]: Lrc20NetworkType.MAINNET,
|
|
45
|
+
[Network.TESTNET]: Lrc20NetworkType.TESTNET,
|
|
46
|
+
[Network.SIGNET]: Lrc20NetworkType.TESTNET,
|
|
47
|
+
[Network.REGTEST]: Lrc20NetworkType.REGTEST,
|
|
48
|
+
[Network.LOCAL]: Lrc20NetworkType.REGTEST,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Utility function to determine the network from a Bitcoin address.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} address - The Bitcoin address
|
|
55
|
+
* @returns {BitcoinNetwork | null} The detected network or null if not detected
|
|
56
|
+
*/
|
|
57
|
+
export function getNetworkFromAddress(address: string) {
|
|
58
|
+
try {
|
|
59
|
+
const decoded = bitcoin.address.fromBech32(address);
|
|
60
|
+
// HRP (human-readable part) determines the network
|
|
61
|
+
if (decoded.prefix === "bc") {
|
|
62
|
+
return BitcoinNetwork.MAINNET;
|
|
63
|
+
} else if (decoded.prefix === "bcrt") {
|
|
64
|
+
return BitcoinNetwork.REGTEST;
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
throw new Error("Invalid Bitcoin address");
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { sha256 } from "@scure/btc-signer/utils";
|
|
2
|
+
|
|
3
|
+
export function proofOfPossessionMessageHashForDepositAddress(
|
|
4
|
+
userPubkey: Uint8Array,
|
|
5
|
+
operatorPubkey: Uint8Array,
|
|
6
|
+
depositAddress: string,
|
|
7
|
+
): Uint8Array {
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const depositAddressBytes = encoder.encode(depositAddress);
|
|
10
|
+
|
|
11
|
+
const proofMsg = new Uint8Array([
|
|
12
|
+
...userPubkey,
|
|
13
|
+
...operatorPubkey,
|
|
14
|
+
...depositAddressBytes,
|
|
15
|
+
]);
|
|
16
|
+
return sha256(proofMsg);
|
|
17
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function validateResponses<T>(
|
|
2
|
+
responses: PromiseSettledResult<T>[],
|
|
3
|
+
): T[] {
|
|
4
|
+
// Get successful responses
|
|
5
|
+
const successfulResponses = responses
|
|
6
|
+
.filter(
|
|
7
|
+
(result): result is PromiseFulfilledResult<T> =>
|
|
8
|
+
result.status === "fulfilled",
|
|
9
|
+
)
|
|
10
|
+
.map((result) => result.value);
|
|
11
|
+
|
|
12
|
+
// If no successful responses, throw with all errors
|
|
13
|
+
if (successfulResponses.length === 0) {
|
|
14
|
+
const errors = responses
|
|
15
|
+
.filter(
|
|
16
|
+
(result): result is PromiseRejectedResult =>
|
|
17
|
+
result.status === "rejected",
|
|
18
|
+
)
|
|
19
|
+
.map((result) => result.reason)
|
|
20
|
+
.join("\n");
|
|
21
|
+
|
|
22
|
+
throw new Error(`All requests failed.\nErrors:\n${errors}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return successfulResponses;
|
|
26
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { bytesToHex, equalBytes } from "@noble/curves/abstract/utils";
|
|
2
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
3
|
+
import { getCrypto } from "./crypto.js";
|
|
4
|
+
|
|
5
|
+
const crypto = getCrypto();
|
|
6
|
+
|
|
7
|
+
type Polynomial = {
|
|
8
|
+
fieldModulus: bigint;
|
|
9
|
+
coefficients: bigint[];
|
|
10
|
+
proofs: Uint8Array[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type SecretShare = {
|
|
14
|
+
fieldModulus: bigint;
|
|
15
|
+
threshold: number;
|
|
16
|
+
index: bigint;
|
|
17
|
+
share: bigint;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type VerifiableSecretShare = SecretShare & {
|
|
21
|
+
proofs: Uint8Array[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function getRandomBigInt(max: bigint): bigint {
|
|
25
|
+
const byteLength = (max.toString(2).length + 7) >> 3;
|
|
26
|
+
const maxBigInt = max;
|
|
27
|
+
|
|
28
|
+
const mask = (1n << BigInt(max.toString(2).length)) - 1n;
|
|
29
|
+
while (true) {
|
|
30
|
+
const randBytes = crypto.getRandomValues(new Uint8Array(byteLength + 1));
|
|
31
|
+
|
|
32
|
+
const randValue = BigInt("0x" + bytesToHex(randBytes)) & mask;
|
|
33
|
+
|
|
34
|
+
if (randValue < maxBigInt) {
|
|
35
|
+
return randValue;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Modular inverse using extended euclidean algorithm
|
|
41
|
+
export function modInverse(a: bigint, m: bigint): bigint {
|
|
42
|
+
// Handle negative numbers by making them positive
|
|
43
|
+
a = ((a % m) + m) % m;
|
|
44
|
+
|
|
45
|
+
let [old_r, r] = [a, m];
|
|
46
|
+
let [old_s, s] = [1n, 0n];
|
|
47
|
+
let [old_t, t] = [0n, 1n];
|
|
48
|
+
|
|
49
|
+
while (r !== 0n) {
|
|
50
|
+
const quotient = old_r / r;
|
|
51
|
+
[old_r, r] = [r, old_r - quotient * r];
|
|
52
|
+
[old_s, s] = [s, old_s - quotient * s];
|
|
53
|
+
[old_t, t] = [t, old_t - quotient * t];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (old_r !== 1n) {
|
|
57
|
+
throw new Error("Modular inverse does not exist");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return ((old_s % m) + m) % m;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Evaluates a polynomial at a given point
|
|
64
|
+
export function evaluatePolynomial(polynomial: Polynomial, x: bigint): bigint {
|
|
65
|
+
let result = 0n;
|
|
66
|
+
for (let i = 0; i < polynomial.coefficients.length; i++) {
|
|
67
|
+
const coeff = polynomial.coefficients[i];
|
|
68
|
+
if (!coeff) {
|
|
69
|
+
throw new Error("Coefficient is undefined");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const xPow = x ** BigInt(i) % polynomial.fieldModulus;
|
|
73
|
+
|
|
74
|
+
result = (result + xPow * coeff) % polynomial.fieldModulus;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Divides two numbers in a given field modulus
|
|
80
|
+
export function fieldDiv(
|
|
81
|
+
numerator: bigint,
|
|
82
|
+
denominator: bigint,
|
|
83
|
+
fieldModulus: bigint,
|
|
84
|
+
): bigint {
|
|
85
|
+
if (denominator === 0n) {
|
|
86
|
+
throw new Error("Division by zero");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const inverse = modInverse(denominator, fieldModulus);
|
|
90
|
+
return (numerator * inverse) % fieldModulus;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Computes the Lagrange coefficient for a given index and a set of points
|
|
94
|
+
export function computerLagrangeCoefficients(
|
|
95
|
+
index: bigint,
|
|
96
|
+
points: SecretShare[],
|
|
97
|
+
) {
|
|
98
|
+
let numerator = 1n;
|
|
99
|
+
let denominator = 1n;
|
|
100
|
+
let fieldModulus = points[0]?.fieldModulus;
|
|
101
|
+
if (!fieldModulus) {
|
|
102
|
+
throw new Error("Field modulus is undefined");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const point of points) {
|
|
106
|
+
if (point.index === index) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
numerator = numerator * point.index;
|
|
110
|
+
const value = point.index - index;
|
|
111
|
+
denominator = denominator * value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return fieldDiv(numerator, denominator, fieldModulus);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Generates a polynomial for secret sharing
|
|
118
|
+
export function generatePolynomialForSecretSharing(
|
|
119
|
+
fieldModulus: bigint,
|
|
120
|
+
secret: bigint,
|
|
121
|
+
degree: number,
|
|
122
|
+
): Polynomial {
|
|
123
|
+
const coefficients: bigint[] = new Array(degree);
|
|
124
|
+
const proofs: Uint8Array[] = new Array(degree);
|
|
125
|
+
|
|
126
|
+
coefficients[0] = secret;
|
|
127
|
+
proofs[0] = secp256k1.ProjectivePoint.fromPrivateKey(secret).toRawBytes(true);
|
|
128
|
+
|
|
129
|
+
for (let i = 1; i < degree; i++) {
|
|
130
|
+
const coefficient = getRandomBigInt(fieldModulus);
|
|
131
|
+
coefficients[i] = coefficient;
|
|
132
|
+
proofs[i] =
|
|
133
|
+
secp256k1.ProjectivePoint.fromPrivateKey(coefficient).toRawBytes(true);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
fieldModulus,
|
|
137
|
+
coefficients,
|
|
138
|
+
proofs: proofs,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Splits a secret into a list of shares
|
|
143
|
+
export function splitSecret(
|
|
144
|
+
fieldModulus: bigint,
|
|
145
|
+
secret: bigint,
|
|
146
|
+
threshold: number,
|
|
147
|
+
numberOfShares: number,
|
|
148
|
+
) {
|
|
149
|
+
const polynomial = generatePolynomialForSecretSharing(
|
|
150
|
+
fieldModulus,
|
|
151
|
+
secret,
|
|
152
|
+
threshold,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const shares: SecretShare[] = [];
|
|
156
|
+
for (let i = 1; i <= numberOfShares; i++) {
|
|
157
|
+
const share = evaluatePolynomial(polynomial, BigInt(i));
|
|
158
|
+
shares.push({
|
|
159
|
+
fieldModulus,
|
|
160
|
+
threshold,
|
|
161
|
+
index: BigInt(i),
|
|
162
|
+
share,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return shares;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Splits a secret into a list of shares with proofs
|
|
170
|
+
export function splitSecretWithProofs(
|
|
171
|
+
secret: bigint,
|
|
172
|
+
fieldModulus: bigint,
|
|
173
|
+
threshold: number,
|
|
174
|
+
numberOfShares: number,
|
|
175
|
+
) {
|
|
176
|
+
const polynomial = generatePolynomialForSecretSharing(
|
|
177
|
+
fieldModulus,
|
|
178
|
+
secret,
|
|
179
|
+
threshold - 1,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const shares: VerifiableSecretShare[] = [];
|
|
183
|
+
for (let i = 1; i <= numberOfShares; i++) {
|
|
184
|
+
const share = evaluatePolynomial(polynomial, BigInt(i));
|
|
185
|
+
shares.push({
|
|
186
|
+
fieldModulus,
|
|
187
|
+
threshold,
|
|
188
|
+
index: BigInt(i),
|
|
189
|
+
share,
|
|
190
|
+
proofs: polynomial.proofs,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return shares;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Recovers a secret from a list of shares
|
|
198
|
+
export function recoverSecret(shares: VerifiableSecretShare[]) {
|
|
199
|
+
if (shares.length === 0) return 0n;
|
|
200
|
+
|
|
201
|
+
const threshold = shares[0]?.threshold;
|
|
202
|
+
const fieldModulus = shares[0]?.fieldModulus;
|
|
203
|
+
|
|
204
|
+
if (!threshold || !fieldModulus) {
|
|
205
|
+
throw new Error("Shares are not valid");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (shares.length < threshold) {
|
|
209
|
+
throw new Error("Not enough shares to recover secret");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let result = 0n;
|
|
213
|
+
for (const share of shares) {
|
|
214
|
+
const coeff = computerLagrangeCoefficients(share.index, shares);
|
|
215
|
+
const item = (share.share * coeff) % fieldModulus;
|
|
216
|
+
|
|
217
|
+
result = (result + item) % fieldModulus;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Validates a share of a secret
|
|
224
|
+
export function validateShare(share: VerifiableSecretShare) {
|
|
225
|
+
const targetPubkey = secp256k1.ProjectivePoint.fromPrivateKey(
|
|
226
|
+
share.share,
|
|
227
|
+
).toRawBytes(true);
|
|
228
|
+
|
|
229
|
+
let resultPubkey = share.proofs[0];
|
|
230
|
+
if (!resultPubkey) {
|
|
231
|
+
throw new Error("Result pubkey is not valid");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (let i = 1; i < share.proofs.length; i++) {
|
|
235
|
+
const pubkey = share.proofs[i];
|
|
236
|
+
if (!pubkey) {
|
|
237
|
+
throw new Error("Pubkey is not valid");
|
|
238
|
+
}
|
|
239
|
+
const value = share.index ** BigInt(i) % share.fieldModulus;
|
|
240
|
+
|
|
241
|
+
const scaledPoint =
|
|
242
|
+
secp256k1.ProjectivePoint.fromHex(pubkey).multiply(value);
|
|
243
|
+
resultPubkey = secp256k1.ProjectivePoint.fromHex(resultPubkey)
|
|
244
|
+
.add(scaledPoint)
|
|
245
|
+
.toRawBytes(true);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!equalBytes(resultPubkey, targetPubkey)) {
|
|
249
|
+
throw new Error("Share is not valid");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Converts a bigint to a private key since imported package doesn't support bigint
|
|
254
|
+
export function bigIntToPrivateKey(value: bigint): Uint8Array {
|
|
255
|
+
const hex = value.toString(16).padStart(64, "0");
|
|
256
|
+
|
|
257
|
+
const bytes = new Uint8Array(32);
|
|
258
|
+
for (let i = 0; i < 32; i++) {
|
|
259
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return bytes;
|
|
263
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
2
|
+
import type { SigningCommitment, SigningNonce } from "../signer/signer.js";
|
|
3
|
+
import {
|
|
4
|
+
SigningCommitment as WasmSigningCommitment,
|
|
5
|
+
SigningNonce as WasmSigningNonce,
|
|
6
|
+
} from "../wasm/spark_bindings.js";
|
|
7
|
+
|
|
8
|
+
export function getRandomSigningNonce(): SigningNonce {
|
|
9
|
+
const binding = secp256k1.utils.randomPrivateKey();
|
|
10
|
+
const hiding = secp256k1.utils.randomPrivateKey();
|
|
11
|
+
return createSigningNonce(binding, hiding);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createSigningNonce(
|
|
15
|
+
binding: Uint8Array,
|
|
16
|
+
hiding: Uint8Array,
|
|
17
|
+
): SigningNonce {
|
|
18
|
+
if (binding.length !== 32 || hiding.length !== 32) {
|
|
19
|
+
throw new Error("Invalid nonce length");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
binding,
|
|
24
|
+
hiding,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getSigningCommitmentFromNonce(
|
|
29
|
+
nonce: SigningNonce,
|
|
30
|
+
): SigningCommitment {
|
|
31
|
+
const bindingPubKey = secp256k1.getPublicKey(nonce.binding, true);
|
|
32
|
+
const hidingPubKey = secp256k1.getPublicKey(nonce.hiding, true);
|
|
33
|
+
return {
|
|
34
|
+
binding: bindingPubKey,
|
|
35
|
+
hiding: hidingPubKey,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function encodeSigningNonceToBytes(nonce: SigningNonce): Uint8Array {
|
|
40
|
+
return new Uint8Array([...nonce.binding, ...nonce.hiding]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function decodeBytesToSigningNonce(bytes: Uint8Array): SigningNonce {
|
|
44
|
+
if (bytes.length !== 64) {
|
|
45
|
+
throw new Error("Invalid nonce length");
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
binding: bytes.slice(32, 64),
|
|
49
|
+
hiding: bytes.slice(0, 32),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createSigningCommitment(
|
|
54
|
+
binding: Uint8Array,
|
|
55
|
+
hiding: Uint8Array,
|
|
56
|
+
): SigningCommitment {
|
|
57
|
+
if (binding.length !== 33 || hiding.length !== 33) {
|
|
58
|
+
throw new Error("Invalid nonce commitment length");
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
binding,
|
|
62
|
+
hiding,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function encodeSigningCommitmentToBytes(
|
|
67
|
+
commitment: SigningCommitment,
|
|
68
|
+
): Uint8Array {
|
|
69
|
+
if (commitment.binding.length !== 33 || commitment.hiding.length !== 33) {
|
|
70
|
+
throw new Error("Invalid nonce commitment length");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return new Uint8Array([...commitment.binding, ...commitment.hiding]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function decodeBytesToSigningCommitment(
|
|
77
|
+
bytes: Uint8Array,
|
|
78
|
+
): SigningCommitment {
|
|
79
|
+
if (bytes.length !== 66) {
|
|
80
|
+
throw new Error("Invalid nonce commitment length");
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
binding: bytes.slice(33, 66),
|
|
84
|
+
hiding: bytes.slice(0, 33),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createWasmSigningNonce(nonce: SigningNonce): WasmSigningNonce {
|
|
89
|
+
return new WasmSigningNonce(nonce.hiding, nonce.binding);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createWasmSigningCommitment(
|
|
93
|
+
commitment: SigningCommitment,
|
|
94
|
+
): WasmSigningCommitment {
|
|
95
|
+
return new WasmSigningCommitment(commitment.hiding, commitment.binding);
|
|
96
|
+
}
|