@utxopia/sdk 0.1.0-alpha.4 → 0.1.0-alpha.5
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/bitcoin/ika.d.ts +4 -3
- package/dist/bitcoin/ika.js +4 -7
- package/dist/client.d.ts +3 -3
- package/dist/crypto-babyjub.d.ts +0 -4
- package/dist/crypto-babyjub.js +0 -4
- package/dist/crypto.d.ts +2 -5
- package/dist/crypto.js +5 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/instructions.d.ts +0 -113
- package/dist/instructions.js +0 -127
- package/dist/poseidon.d.ts +0 -4
- package/dist/poseidon.js +0 -8
- package/dist/prover/index.d.ts +0 -1
- package/dist/prover/web.d.ts +0 -15
- package/dist/prover/web.js +2 -49
- package/dist/psbt.d.ts +2 -1
- package/dist/psbt.js +6 -2
- package/dist/spend-doc.js +1 -1
- package/dist/stealth.d.ts +6 -12
- package/dist/stealth.js +0 -14
- package/dist/taproot.d.ts +32 -4
- package/dist/taproot.js +54 -32
- package/package.json +1 -32
- package/src/bitcoin/ika.ts +6 -11
- package/src/client.ts +4 -4
- package/src/crypto-babyjub.ts +0 -5
- package/src/crypto.ts +5 -24
- package/src/index.ts +4 -0
- package/src/instructions.ts +0 -214
- package/src/poseidon.ts +0 -9
- package/src/prover/index.ts +0 -8
- package/src/prover/web.ts +3 -72
- package/src/psbt.ts +8 -3
- package/src/spend-doc.ts +1 -2
- package/src/stealth.ts +7 -23
- package/src/taproot.ts +74 -39
package/dist/psbt.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import * as btc from "@scure/btc-signer";
|
|
13
13
|
import { hex } from "@scure/base";
|
|
14
|
-
import { DEPOSIT_OP_RETURN_SIZE, createOpReturnScriptFromPayload } from "./taproot";
|
|
14
|
+
import { DEPOSIT_OP_RETURN_SIZE, createOpReturnScriptFromPayload, bech32Hrp } from "./taproot";
|
|
15
15
|
// =============================================================================
|
|
16
16
|
// Constants
|
|
17
17
|
// =============================================================================
|
|
@@ -64,7 +64,11 @@ export function buildDepositPsbt(params) {
|
|
|
64
64
|
if (opReturnPayload && opReturnPayload.length !== DEPOSIT_OP_RETURN_SIZE) {
|
|
65
65
|
throw new Error(`OP_RETURN payload must be ${DEPOSIT_OP_RETURN_SIZE} bytes, got ${opReturnPayload.length}`);
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
// Regtest shares testnet's version bytes but not its bech32 hrp, so folding it
|
|
68
|
+
// into TEST_NETWORK leaves every bcrt1 address undecodable.
|
|
69
|
+
const btcNetwork = network === "mainnet"
|
|
70
|
+
? btc.NETWORK
|
|
71
|
+
: { ...btc.TEST_NETWORK, bech32: bech32Hrp(network) };
|
|
68
72
|
// Calculate total input value
|
|
69
73
|
const totalInput = senderUtxos.reduce((sum, u) => sum + u.value, 0);
|
|
70
74
|
// Detect input type from first UTXO
|
package/dist/spend-doc.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Poseidon(merkleRoot, boundParamsHash, nullifiers.., commitmentsOut..).
|
|
12
12
|
*/
|
|
13
13
|
import { computeSolanaDomainBoundParamsHash, createRedeemBoundParams, createTransferBoundParams, createUnshieldBoundParams, } from "./bound-params";
|
|
14
|
+
import { bytesToHex as hex } from "./crypto";
|
|
14
15
|
export class SpendDocMismatch extends Error {
|
|
15
16
|
}
|
|
16
17
|
function fmt(raw, decimals) {
|
|
@@ -20,7 +21,6 @@ function fmt(raw, decimals) {
|
|
|
20
21
|
const frac = decimals === 0 ? "" : s.slice(s.length - decimals).replace(/0+$/, "");
|
|
21
22
|
return `${neg ? "-" : ""}${whole}${frac ? `.${frac}` : ""}`;
|
|
22
23
|
}
|
|
23
|
-
const hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
24
24
|
function sortedValues(v) {
|
|
25
25
|
return [...v].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).join(",");
|
|
26
26
|
}
|
package/dist/stealth.d.ts
CHANGED
|
@@ -189,8 +189,8 @@ export interface NonInteractiveDepositWithRefundResult extends NonInteractiveDep
|
|
|
189
189
|
* @param network - Bitcoin network for address encoding
|
|
190
190
|
* @param userRefundPubkey - Optional 32-byte x-only pubkey for refund script path
|
|
191
191
|
*/
|
|
192
|
-
export declare function createNonInteractiveDeposit(recipientMeta: StealthMetaAddress, custodyInternalKey: Uint8Array, network?:
|
|
193
|
-
export declare function createNonInteractiveDeposit(recipientMeta: StealthMetaAddress, custodyInternalKey: Uint8Array, network:
|
|
192
|
+
export declare function createNonInteractiveDeposit(recipientMeta: StealthMetaAddress, custodyInternalKey: Uint8Array, network?: BitcoinNetwork, userRefundPubkey?: undefined, opReturnContext?: DepositOpReturnContext): Promise<NonInteractiveDepositResult>;
|
|
193
|
+
export declare function createNonInteractiveDeposit(recipientMeta: StealthMetaAddress, custodyInternalKey: Uint8Array, network: BitcoinNetwork, userRefundPubkey: Uint8Array, opReturnContext: DepositOpReturnContext): Promise<NonInteractiveDepositWithRefundResult>;
|
|
194
194
|
/**
|
|
195
195
|
* Create a non-interactive deposit directly to an Ika-controlled vault.
|
|
196
196
|
*
|
|
@@ -199,7 +199,7 @@ export declare function createNonInteractiveDeposit(recipientMeta: StealthMetaAd
|
|
|
199
199
|
* in OP_RETURN(header || poolTag || ephemeralPub || npk), and the destination chain
|
|
200
200
|
* credits the note from that transaction.
|
|
201
201
|
*/
|
|
202
|
-
export declare function createDirectVaultDeposit(recipientMeta: StealthMetaAddress, vaultXOnlyPubkey: Uint8Array, network?:
|
|
202
|
+
export declare function createDirectVaultDeposit(recipientMeta: StealthMetaAddress, vaultXOnlyPubkey: Uint8Array, network?: BitcoinNetwork, opReturnContext?: DepositOpReturnContext): Promise<NonInteractiveDepositResult>;
|
|
203
203
|
/**
|
|
204
204
|
* The delegable half of deposit recovery.
|
|
205
205
|
*
|
|
@@ -322,7 +322,7 @@ export interface TweakDepositResult {
|
|
|
322
322
|
* with no OP_RETURN is invisible to block scanning, so an unregistered address
|
|
323
323
|
* is one nobody is watching.
|
|
324
324
|
*/
|
|
325
|
-
export declare function createTweakDeposit(recipientMeta: StealthMetaAddress, vaultXOnlyPubkey: Uint8Array, recovery: DepositRecoveryMaterial, network?:
|
|
325
|
+
export declare function createTweakDeposit(recipientMeta: StealthMetaAddress, vaultXOnlyPubkey: Uint8Array, recovery: DepositRecoveryMaterial, network?: BitcoinNetwork): Promise<TweakDepositResult>;
|
|
326
326
|
/**
|
|
327
327
|
* Create a non-interactive deposit using the current SDK config.
|
|
328
328
|
*
|
|
@@ -333,7 +333,7 @@ export declare function createTweakDeposit(recipientMeta: StealthMetaAddress, va
|
|
|
333
333
|
* credits the note by SPV-verifying that deposit transaction directly. Legacy sweep-mode
|
|
334
334
|
* address derivation is intentionally not selected from config anymore.
|
|
335
335
|
*/
|
|
336
|
-
export declare function createDepositFromConfig(recipientMeta: StealthMetaAddress, network?:
|
|
336
|
+
export declare function createDepositFromConfig(recipientMeta: StealthMetaAddress, network?: BitcoinNetwork, opReturnContext?: DepositOpReturnContext): Promise<NonInteractiveDepositResult>;
|
|
337
337
|
export declare function isDirectVaultDepositMode(mode?: string): boolean;
|
|
338
338
|
/**
|
|
339
339
|
* Choose the Taproot internal key for deposit-address derivation.
|
|
@@ -448,6 +448,7 @@ export declare function scanUnifiedNotesMulti(source: WalletSignerAdapter | UTXO
|
|
|
448
448
|
*/
|
|
449
449
|
export declare function scanUnifiedNotes(source: WalletSignerAdapter | UTXOpiaKeys, announcements: OnChainStealthAnnouncement[], tokenId: bigint): Promise<ScannedNote[]>;
|
|
450
450
|
import type { Address } from "@solana/kit";
|
|
451
|
+
import type { BitcoinNetwork } from "./taproot";
|
|
451
452
|
export interface ConnectionAdapter {
|
|
452
453
|
getAccountInfo: (pubkey: Address) => Promise<{
|
|
453
454
|
data: Uint8Array;
|
|
@@ -480,13 +481,6 @@ export declare function packEncryptedAmountWithSign(encryptedAmount: Uint8Array,
|
|
|
480
481
|
* Convert StealthOutputData to circuit-ready format
|
|
481
482
|
*/
|
|
482
483
|
export declare function packStealthOutputForCircuit(output: StealthOutputData): CircuitStealthOutput;
|
|
483
|
-
/**
|
|
484
|
-
* Unpack encrypted amount from packed Field element
|
|
485
|
-
*/
|
|
486
|
-
export declare function unpackEncryptedAmountWithSign(packed: bigint): {
|
|
487
|
-
encryptedAmount: Uint8Array;
|
|
488
|
-
ySign: boolean;
|
|
489
|
-
};
|
|
490
484
|
/**
|
|
491
485
|
* Create stealth output data for a self-send (change output)
|
|
492
486
|
*/
|
package/dist/stealth.js
CHANGED
|
@@ -737,20 +737,6 @@ export function packStealthOutputForCircuit(output) {
|
|
|
737
737
|
encryptedAmountWithSign,
|
|
738
738
|
};
|
|
739
739
|
}
|
|
740
|
-
/**
|
|
741
|
-
* Unpack encrypted amount from packed Field element
|
|
742
|
-
*/
|
|
743
|
-
export function unpackEncryptedAmountWithSign(packed) {
|
|
744
|
-
const ySign = (packed & (1n << 64n)) !== 0n;
|
|
745
|
-
const amount = packed & ((1n << 64n) - 1n);
|
|
746
|
-
const encryptedAmount = new Uint8Array(8);
|
|
747
|
-
let temp = amount;
|
|
748
|
-
for (let i = 0; i < 8; i++) {
|
|
749
|
-
encryptedAmount[i] = Number(temp & 0xffn);
|
|
750
|
-
temp >>= 8n;
|
|
751
|
-
}
|
|
752
|
-
return { encryptedAmount, ySign };
|
|
753
|
-
}
|
|
754
740
|
/**
|
|
755
741
|
* Create stealth output data for a self-send (change output)
|
|
756
742
|
*/
|
package/dist/taproot.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* @param internalKey - Configured FROST/Ika custody key (x-only, 32 bytes; required)
|
|
19
19
|
* @returns Taproot address (bc1p... or tb1p...)
|
|
20
20
|
*/
|
|
21
|
-
export declare function deriveTaprootAddress(commitment: Uint8Array, network?:
|
|
21
|
+
export declare function deriveTaprootAddress(commitment: Uint8Array, network?: BitcoinNetwork, internalKey?: Uint8Array): {
|
|
22
22
|
address: string;
|
|
23
23
|
outputKey: Uint8Array;
|
|
24
24
|
tweak: Uint8Array;
|
|
@@ -64,7 +64,7 @@ export declare function depositLeafScript(commitment: Uint8Array, ikaXOnlyPubkey
|
|
|
64
64
|
* Returns everything the sweeper needs to spend it: a script-path witness is
|
|
65
65
|
* `[signature, leafScript, controlBlock]`.
|
|
66
66
|
*/
|
|
67
|
-
export declare function deriveDepositAddress(commitment: Uint8Array, ikaXOnlyPubkey: Uint8Array, network?:
|
|
67
|
+
export declare function deriveDepositAddress(commitment: Uint8Array, ikaXOnlyPubkey: Uint8Array, network?: BitcoinNetwork): {
|
|
68
68
|
address: string;
|
|
69
69
|
outputKey: Uint8Array;
|
|
70
70
|
leafScript: Uint8Array;
|
|
@@ -80,6 +80,34 @@ export declare function deriveDepositAddress(commitment: Uint8Array, ikaXOnlyPub
|
|
|
80
80
|
* @returns true if address matches expected derivation
|
|
81
81
|
*/
|
|
82
82
|
export declare function verifyTaprootAddress(address: string, commitment: Uint8Array, internalKey?: Uint8Array): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* The networks whose addresses this SDK derives.
|
|
85
|
+
*
|
|
86
|
+
* Kept as one alias because the members drifted when it was spelled out at each
|
|
87
|
+
* call site: psbt.ts folded regtest into testnet while taproot.ts did not, and
|
|
88
|
+
* the two disagreed about what a bcrt1 address was.
|
|
89
|
+
*/
|
|
90
|
+
export type BitcoinNetwork = "mainnet" | "testnet" | "signet" | "regtest";
|
|
91
|
+
/**
|
|
92
|
+
* bech32 human-readable prefix for a Bitcoin network.
|
|
93
|
+
*
|
|
94
|
+
* Regtest is the reason this exists: it shares testnet's version bytes but not
|
|
95
|
+
* its prefix, and every place that folded the two together produced addresses
|
|
96
|
+
* no regtest node would accept.
|
|
97
|
+
*/
|
|
98
|
+
export declare function bech32Hrp(network: BitcoinNetwork): string;
|
|
99
|
+
/**
|
|
100
|
+
* The network an address's bech32 prefix names, or null if it names none.
|
|
101
|
+
*
|
|
102
|
+
* Null rather than a testnet default: an unrecognised prefix is an address this
|
|
103
|
+
* SDK cannot place, and quietly calling it testnet is how bcrt1 addresses ended
|
|
104
|
+
* up classified as testnet everywhere.
|
|
105
|
+
*/
|
|
106
|
+
export declare function networkForHrp(prefix: string): BitcoinNetwork | null;
|
|
107
|
+
/**
|
|
108
|
+
* Encode an x-only output key as a witness-v1 (P2TR) address.
|
|
109
|
+
*/
|
|
110
|
+
export declare function p2trAddress(outputKey: Uint8Array, network: BitcoinNetwork): string;
|
|
83
111
|
/**
|
|
84
112
|
* Generate a P2TR (Pay-to-Taproot) script pubkey
|
|
85
113
|
*
|
|
@@ -100,7 +128,7 @@ export declare function parseP2TRScriptPubkey(scriptPubkey: Uint8Array): Uint8Ar
|
|
|
100
128
|
export declare function isValidBitcoinAddress(address: string): {
|
|
101
129
|
valid: boolean;
|
|
102
130
|
type: "p2pkh" | "p2sh" | "p2wpkh" | "p2wsh" | "p2tr" | "unknown";
|
|
103
|
-
network:
|
|
131
|
+
network: BitcoinNetwork | "unknown";
|
|
104
132
|
};
|
|
105
133
|
/** Destination chain encoded in the compact deposit OP_RETURN header. */
|
|
106
134
|
export declare const DEPOSIT_DESTINATION_CHAIN: {
|
|
@@ -201,7 +229,7 @@ export declare function computeTapLeafHash(script: Uint8Array, leafVersion?: num
|
|
|
201
229
|
* @param internalKey - 32-byte x-only FROST group public key
|
|
202
230
|
* @param network - Bitcoin network for address encoding
|
|
203
231
|
*/
|
|
204
|
-
export declare function deriveTaprootAddressWithRefund(npk: Uint8Array, userRefundPubkey: Uint8Array, internalKey: Uint8Array, network?:
|
|
232
|
+
export declare function deriveTaprootAddressWithRefund(npk: Uint8Array, userRefundPubkey: Uint8Array, internalKey: Uint8Array, network?: BitcoinNetwork): {
|
|
205
233
|
address: string;
|
|
206
234
|
outputKey: Uint8Array;
|
|
207
235
|
merkleRoot: Uint8Array;
|
package/dist/taproot.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
9
9
|
import { taggedHash, hexToBytes, bytesToHex } from "./crypto";
|
|
10
|
-
import
|
|
10
|
+
import { bech32, bech32m } from "@scure/base";
|
|
11
11
|
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
12
12
|
// Never use the secp256k1 generator as a custody key. Its discrete log is
|
|
13
13
|
// public, so a key-path output derived from it is sweepable by anyone.
|
|
@@ -59,11 +59,7 @@ export function deriveTaprootAddress(commitment, network = "testnet", internalKe
|
|
|
59
59
|
// x-only output key (BIP-340): drop prefix from compressed form
|
|
60
60
|
const outputKeyHex = outputPoint.toHex(true); // 33-byte compressed hex
|
|
61
61
|
const outputKey = hexToBytes(outputKeyHex.slice(2)); // drop "02"/"03" prefix
|
|
62
|
-
|
|
63
|
-
const hrp = network === "mainnet" ? "bc" : network === "regtest" ? "bcrt" : "tb";
|
|
64
|
-
const words = bech32.bech32m.toWords(outputKey);
|
|
65
|
-
// Witness version 1 for taproot
|
|
66
|
-
const address = bech32.bech32m.encode(hrp, [1, ...words]);
|
|
62
|
+
const address = p2trAddress(outputKey, network);
|
|
67
63
|
return {
|
|
68
64
|
address,
|
|
69
65
|
outputKey,
|
|
@@ -151,20 +147,56 @@ export function deriveDepositAddress(commitment, ikaXOnlyPubkey, network = "test
|
|
|
151
147
|
*/
|
|
152
148
|
export function verifyTaprootAddress(address, commitment, internalKey) {
|
|
153
149
|
try {
|
|
154
|
-
const decoded =
|
|
150
|
+
const decoded = bech32m.decode(address);
|
|
155
151
|
const witnessVersion = decoded.words[0];
|
|
156
152
|
if (witnessVersion !== 1) {
|
|
157
153
|
return false;
|
|
158
154
|
}
|
|
159
|
-
const
|
|
160
|
-
|
|
155
|
+
const network = networkForHrp(decoded.prefix);
|
|
156
|
+
if (!network)
|
|
157
|
+
return false;
|
|
161
158
|
const expected = deriveTaprootAddress(commitment, network, internalKey);
|
|
162
|
-
|
|
159
|
+
// Compare the whole address, not just the output key: the key is identical
|
|
160
|
+
// on every network, so an output-key match said nothing about the encoding.
|
|
161
|
+
// bech32 is case-insensitive, hence the fold.
|
|
162
|
+
return address.toLowerCase() === expected.address;
|
|
163
163
|
}
|
|
164
164
|
catch {
|
|
165
165
|
return false;
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* bech32 human-readable prefix for a Bitcoin network.
|
|
170
|
+
*
|
|
171
|
+
* Regtest is the reason this exists: it shares testnet's version bytes but not
|
|
172
|
+
* its prefix, and every place that folded the two together produced addresses
|
|
173
|
+
* no regtest node would accept.
|
|
174
|
+
*/
|
|
175
|
+
export function bech32Hrp(network) {
|
|
176
|
+
return network === "mainnet" ? "bc" : network === "regtest" ? "bcrt" : "tb";
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The network an address's bech32 prefix names, or null if it names none.
|
|
180
|
+
*
|
|
181
|
+
* Null rather than a testnet default: an unrecognised prefix is an address this
|
|
182
|
+
* SDK cannot place, and quietly calling it testnet is how bcrt1 addresses ended
|
|
183
|
+
* up classified as testnet everywhere.
|
|
184
|
+
*/
|
|
185
|
+
export function networkForHrp(prefix) {
|
|
186
|
+
return prefix === "bc"
|
|
187
|
+
? "mainnet"
|
|
188
|
+
: prefix === "tb"
|
|
189
|
+
? "testnet"
|
|
190
|
+
: prefix === "bcrt"
|
|
191
|
+
? "regtest"
|
|
192
|
+
: null;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Encode an x-only output key as a witness-v1 (P2TR) address.
|
|
196
|
+
*/
|
|
197
|
+
export function p2trAddress(outputKey, network) {
|
|
198
|
+
return bech32m.encode(bech32Hrp(network), [1, ...bech32m.toWords(outputKey)]);
|
|
199
|
+
}
|
|
168
200
|
/**
|
|
169
201
|
* Generate a P2TR (Pay-to-Taproot) script pubkey
|
|
170
202
|
*
|
|
@@ -203,32 +235,24 @@ export function parseP2TRScriptPubkey(scriptPubkey) {
|
|
|
203
235
|
export function isValidBitcoinAddress(address) {
|
|
204
236
|
try {
|
|
205
237
|
// Bech32m (Taproot)
|
|
206
|
-
if (address.startsWith("bc1p") ||
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
};
|
|
238
|
+
if (address.startsWith("bc1p") ||
|
|
239
|
+
address.startsWith("tb1p") ||
|
|
240
|
+
address.startsWith("bcrt1p")) {
|
|
241
|
+
const decoded = bech32m.decode(address);
|
|
242
|
+
const network = networkForHrp(decoded.prefix);
|
|
243
|
+
if (network && decoded.words[0] === 1 && decoded.words.length === 53) {
|
|
244
|
+
return { valid: true, type: "p2tr", network };
|
|
214
245
|
}
|
|
215
246
|
}
|
|
216
247
|
// Bech32 (SegWit v0)
|
|
217
248
|
if (address.startsWith("bc1q") ||
|
|
218
249
|
address.startsWith("tb1q") ||
|
|
219
250
|
address.startsWith("bcrt1q")) {
|
|
220
|
-
const decoded = bech32.
|
|
221
|
-
|
|
251
|
+
const decoded = bech32.decode(address);
|
|
252
|
+
const network = networkForHrp(decoded.prefix);
|
|
253
|
+
if (network && decoded.words[0] === 0) {
|
|
222
254
|
const type = decoded.words.length === 33 ? "p2wpkh" : "p2wsh";
|
|
223
|
-
return {
|
|
224
|
-
valid: true,
|
|
225
|
-
type,
|
|
226
|
-
network: decoded.prefix === "bc"
|
|
227
|
-
? "mainnet"
|
|
228
|
-
: decoded.prefix === "bcrt"
|
|
229
|
-
? "testnet"
|
|
230
|
-
: "testnet",
|
|
231
|
-
};
|
|
255
|
+
return { valid: true, type, network };
|
|
232
256
|
}
|
|
233
257
|
}
|
|
234
258
|
// Legacy (base58check)
|
|
@@ -533,9 +557,7 @@ export function deriveTaprootAddressWithRefund(npk, userRefundPubkey, internalKe
|
|
|
533
557
|
controlBlock[0] = 0xc0 | parityBit;
|
|
534
558
|
controlBlock.set(internalKey, 1);
|
|
535
559
|
// 7. Encode as bech32m address
|
|
536
|
-
const
|
|
537
|
-
const words = bech32.bech32m.toWords(outputKey);
|
|
538
|
-
const address = bech32.bech32m.encode(hrp, [1, ...words]);
|
|
560
|
+
const address = p2trAddress(outputKey, network);
|
|
539
561
|
return {
|
|
540
562
|
address,
|
|
541
563
|
outputKey,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utxopia/sdk",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.5",
|
|
4
4
|
"description": "UTXOpia SDK - Private Bitcoin across Solana and Sui",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,40 +27,10 @@
|
|
|
27
27
|
"import": "./dist/prover/mobile.js",
|
|
28
28
|
"default": "./dist/prover/mobile.js"
|
|
29
29
|
},
|
|
30
|
-
"./stealth": {
|
|
31
|
-
"types": "./dist/stealth/index.d.ts",
|
|
32
|
-
"import": "./dist/stealth/index.js",
|
|
33
|
-
"default": "./dist/stealth/index.js"
|
|
34
|
-
},
|
|
35
30
|
"./bitcoin": {
|
|
36
31
|
"types": "./dist/bitcoin/index.d.ts",
|
|
37
32
|
"import": "./dist/bitcoin/index.js",
|
|
38
33
|
"default": "./dist/bitcoin/index.js"
|
|
39
|
-
},
|
|
40
|
-
"./solana": {
|
|
41
|
-
"types": "./dist/solana/index.d.ts",
|
|
42
|
-
"import": "./dist/solana/index.js",
|
|
43
|
-
"default": "./dist/solana/index.js"
|
|
44
|
-
},
|
|
45
|
-
"./watcher": {
|
|
46
|
-
"types": "./dist/watcher/index.d.ts",
|
|
47
|
-
"import": "./dist/watcher/index.js",
|
|
48
|
-
"default": "./dist/watcher/index.js"
|
|
49
|
-
},
|
|
50
|
-
"./watcher/web": {
|
|
51
|
-
"types": "./dist/watcher/web.d.ts",
|
|
52
|
-
"import": "./dist/watcher/web.js",
|
|
53
|
-
"default": "./dist/watcher/web.js"
|
|
54
|
-
},
|
|
55
|
-
"./watcher/native": {
|
|
56
|
-
"types": "./dist/watcher/native.d.ts",
|
|
57
|
-
"import": "./dist/watcher/native.js",
|
|
58
|
-
"default": "./dist/watcher/native.js"
|
|
59
|
-
},
|
|
60
|
-
"./react": {
|
|
61
|
-
"types": "./dist/react/index.d.ts",
|
|
62
|
-
"import": "./dist/react/index.js",
|
|
63
|
-
"default": "./dist/react/index.js"
|
|
64
34
|
}
|
|
65
35
|
},
|
|
66
36
|
"files": [
|
|
@@ -93,7 +63,6 @@
|
|
|
93
63
|
"@scure/btc-signer": "2.0.1",
|
|
94
64
|
"@solana-program/system": "^0.10.0",
|
|
95
65
|
"@solana/kit": "^5.5.0",
|
|
96
|
-
"bech32": "^2.0.0",
|
|
97
66
|
"circomlibjs": "^0.1.7",
|
|
98
67
|
"poseidon-lite": "0.3.0"
|
|
99
68
|
},
|
package/src/bitcoin/ika.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { taggedHash, hexToBytes, bytesToHex } from "../crypto";
|
|
11
|
-
import {
|
|
11
|
+
import { p2trAddress, type BitcoinNetwork } from "../taproot";
|
|
12
12
|
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -33,12 +33,12 @@ export type IkaDWalletRef =
|
|
|
33
33
|
* address = bech32m(hrp, [witness_version=1, ...words(output_key)])
|
|
34
34
|
*
|
|
35
35
|
* @param ref The Ika dWallet reference (literal pubkey, or future async id).
|
|
36
|
-
* @param network
|
|
36
|
+
* @param network BitcoinNetwork
|
|
37
37
|
* @returns The P2TR address (`bc1p…` / `tb1p…` / `bcrt1p…`)
|
|
38
38
|
*/
|
|
39
39
|
export function deriveCustodyAddressFromIkaDWallet(
|
|
40
40
|
ref: IkaDWalletRef,
|
|
41
|
-
network:
|
|
41
|
+
network: BitcoinNetwork
|
|
42
42
|
): string {
|
|
43
43
|
const xonly = extractXOnly(ref);
|
|
44
44
|
const tweak = taggedHash("TapTweak", xonly);
|
|
@@ -52,10 +52,7 @@ export function deriveCustodyAddressFromIkaDWallet(
|
|
|
52
52
|
// Drop the 1-byte parity prefix to get the x-only output key.
|
|
53
53
|
const outputKey = hexToBytes(outputPoint.toHex(true).slice(2));
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
network === "mainnet" ? "bc" : network === "regtest" ? "bcrt" : "tb";
|
|
57
|
-
const words = bech32m.toWords(outputKey);
|
|
58
|
-
return bech32m.encode(hrp, [1, ...words]);
|
|
55
|
+
return p2trAddress(outputKey, network);
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
/**
|
|
@@ -67,14 +64,12 @@ export function deriveCustodyAddressFromIkaDWallet(
|
|
|
67
64
|
*/
|
|
68
65
|
export function deriveRawXOnlyP2TRAddress(
|
|
69
66
|
xonlyPubkey: Uint8Array,
|
|
70
|
-
network:
|
|
67
|
+
network: BitcoinNetwork
|
|
71
68
|
): string {
|
|
72
69
|
if (xonlyPubkey.length !== 32) {
|
|
73
70
|
throw new Error("xonlyPubkey must be 32 bytes");
|
|
74
71
|
}
|
|
75
|
-
|
|
76
|
-
network === "mainnet" ? "bc" : network === "regtest" ? "bcrt" : "tb";
|
|
77
|
-
return bech32m.encode(hrp, [1, ...bech32m.toWords(xonlyPubkey)]);
|
|
72
|
+
return p2trAddress(xonlyPubkey, network);
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
function extractXOnly(ref: IkaDWalletRef): Uint8Array {
|
package/src/client.ts
CHANGED
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
import { selectUtxos, type UtxoDescriptor } from "./psbt";
|
|
57
57
|
import { hexToBytes, bytesToHex, bigintToBytes } from "./crypto";
|
|
58
58
|
import { EventClient } from "./event-client";
|
|
59
|
-
import { type DepositOpReturnContext } from "./taproot";
|
|
59
|
+
import { type DepositOpReturnContext, type BitcoinNetwork } from "./taproot";
|
|
60
60
|
|
|
61
61
|
// ─── Types ──────────────────────────────────────────────────────────
|
|
62
62
|
|
|
@@ -425,7 +425,7 @@ export class UTXOpiaClient {
|
|
|
425
425
|
depositIndex: number;
|
|
426
426
|
ikaXOnlyPubkey: Uint8Array;
|
|
427
427
|
recipient?: StealthMetaAddress;
|
|
428
|
-
network?:
|
|
428
|
+
network?: BitcoinNetwork;
|
|
429
429
|
}): Promise<TweakDepositResult> {
|
|
430
430
|
if (!this._keys) {
|
|
431
431
|
throw new Error("No keys (login first)");
|
|
@@ -457,7 +457,7 @@ export class UTXOpiaClient {
|
|
|
457
457
|
*/
|
|
458
458
|
async prepareDeposit(opts: {
|
|
459
459
|
recipient?: StealthMetaAddress;
|
|
460
|
-
network?:
|
|
460
|
+
network?: BitcoinNetwork;
|
|
461
461
|
opReturnContext: DepositOpReturnContext;
|
|
462
462
|
}): Promise<NonInteractiveDepositResult> {
|
|
463
463
|
const meta = opts.recipient ?? this._stealthAddress;
|
|
@@ -631,7 +631,7 @@ export class UTXOpiaClient {
|
|
|
631
631
|
}
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
-
function sdkBitcoinNetworkToAddressNetwork(network: NetworkConfig["bitcoinNetwork"]):
|
|
634
|
+
function sdkBitcoinNetworkToAddressNetwork(network: NetworkConfig["bitcoinNetwork"]): BitcoinNetwork {
|
|
635
635
|
if (network === "mainnet") return "mainnet";
|
|
636
636
|
if (network === "regtest") return "regtest";
|
|
637
637
|
return "testnet";
|
package/src/crypto-babyjub.ts
CHANGED
|
@@ -35,11 +35,6 @@ export const BABYJUB_D = 168696n;
|
|
|
35
35
|
export const BABYJUB_ORDER =
|
|
36
36
|
2736030358979909402780800718157159386076813972158567259200215660948447373041n;
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
* Baby Jubjub cofactor
|
|
40
|
-
*/
|
|
41
|
-
export const BABYJUB_COFACTOR = 8n;
|
|
42
|
-
|
|
43
38
|
/**
|
|
44
39
|
* Generator point (BASE8) - matches circomlib's BabyPbk() generator
|
|
45
40
|
* This is the base point of the prime-order subgroup (cofactor-cleared).
|
package/src/crypto.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
17
|
+
import { bytesToHex as nobleBytesToHex, hexToBytes as nobleHexToBytes } from "@noble/hashes/utils.js";
|
|
17
18
|
|
|
18
19
|
// =============================================================================
|
|
19
20
|
// Field Constants
|
|
@@ -110,22 +111,15 @@ export function bytesToBigint(bytes: Uint8Array): bigint {
|
|
|
110
111
|
* Convert hex string to Uint8Array
|
|
111
112
|
*/
|
|
112
113
|
export function hexToBytes(hex: string): Uint8Array {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
bytes[i / 2] = parseInt(cleanHex.substr(i, 2), 16);
|
|
117
|
-
}
|
|
118
|
-
return bytes;
|
|
114
|
+
// The 0x prefix is ours; noble rejects it. Everything after is noble's, which
|
|
115
|
+
// also means malformed hex now throws instead of decoding to zero bytes.
|
|
116
|
+
return nobleHexToBytes(hex.startsWith("0x") ? hex.slice(2) : hex);
|
|
119
117
|
}
|
|
120
118
|
|
|
121
119
|
/**
|
|
122
120
|
* Convert Uint8Array to hex string
|
|
123
121
|
*/
|
|
124
|
-
export
|
|
125
|
-
return Array.from(bytes)
|
|
126
|
-
.map((b) => b.toString(16).padStart(2, "0"))
|
|
127
|
-
.join("");
|
|
128
|
-
}
|
|
122
|
+
export const bytesToHex = nobleBytesToHex;
|
|
129
123
|
|
|
130
124
|
// =============================================================================
|
|
131
125
|
// Hashing Utilities
|
|
@@ -184,16 +178,3 @@ export function scalarFromBytes(bytes: Uint8Array): bigint {
|
|
|
184
178
|
}
|
|
185
179
|
return mod(result, BABYJUB_ORDER);
|
|
186
180
|
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Convert a bigint scalar to 32 bytes (big-endian)
|
|
190
|
-
*/
|
|
191
|
-
export function scalarToBytes(scalar: bigint): Uint8Array {
|
|
192
|
-
const bytes = new Uint8Array(32);
|
|
193
|
-
let temp = mod(scalar, BABYJUB_ORDER);
|
|
194
|
-
for (let i = 31; i >= 0; i--) {
|
|
195
|
-
bytes[i] = Number(temp & 0xffn);
|
|
196
|
-
temp = temp >> 8n;
|
|
197
|
-
}
|
|
198
|
-
return bytes;
|
|
199
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -349,6 +349,10 @@ export {
|
|
|
349
349
|
DEPOSIT_OP_RETURN_VERSION,
|
|
350
350
|
DEPOSIT_POOL_TAG_SIZE,
|
|
351
351
|
DEPOSIT_OP_RETURN_SIZE,
|
|
352
|
+
bech32Hrp,
|
|
353
|
+
networkForHrp,
|
|
354
|
+
p2trAddress,
|
|
355
|
+
type BitcoinNetwork,
|
|
352
356
|
type DepositDestinationChain,
|
|
353
357
|
type DepositBitcoinNetwork,
|
|
354
358
|
type DepositOpReturnContext,
|