@qrkit/core 0.3.1 → 0.4.0

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Framework-agnostic protocol core for QR-based airgapped wallet flows. Designed for use in browser-based dApps.
4
4
 
5
- Handles the ERC-4527 / UR / CBOR stack: decoding scanned QR exports, deriving EVM addresses, building sign requests, and parsing signature responses. No DOM, no React, no external services.
5
+ Handles the ERC-4527 / UR / CBOR stack: decoding scanned QR exports, deriving EVM and BTC addresses, building sign requests, carrying PSBTs, and parsing signature responses. No DOM, no React, no external services.
6
6
 
7
7
  ## Install
8
8
 
@@ -14,41 +14,50 @@ pnpm add @qrkit/core
14
14
 
15
15
  ### 1. Parse the connection QR from the wallet
16
16
 
17
- Scan the `crypto-hdkey` or `crypto-account` QR exported by the hardware wallet, then derive the EVM account:
17
+ Scan the `crypto-hdkey`, `crypto-account`, or `crypto-multi-accounts` QR exported by the hardware wallet, then derive the accounts you need:
18
18
 
19
19
  ```ts
20
- import { parseConnection } from '@qrkit/core'
20
+ import { parseConnection } from "@qrkit/core";
21
21
 
22
22
  // scannedUR comes from a QR scanner — { type: string, cbor: Uint8Array }
23
- const [account] = parseConnection(scannedUR, { chains: ['evm'] })
24
-
25
- account.address // EIP-55 checksummed address
26
- account.publicKey // compressed pubkey hex
27
- account.sourceFingerprint // master key fingerprint — required for signing
23
+ const accounts = parseConnection(scannedUR, { chains: ["evm", "btc"] });
24
+ const evm = accounts.find((account) => account.chain === "evm");
25
+ const btc = accounts.find((account) => account.chain === "btc");
26
+
27
+ evm?.address; // EIP-55 checksummed address
28
+ btc?.address; // BTC address, e.g. bc1q...
29
+ btc?.scriptType; // 'p2wpkh' | 'p2sh-p2wpkh' | 'p2pkh'
30
+ evm?.sourceFingerprint; // master key fingerprint — required for signing
28
31
  ```
29
32
 
30
- ### 2. Build a sign request
33
+ `parseConnection()` can return multiple accounts from one scan, including mixed EVM and BTC results from `crypto-multi-accounts`.
34
+
35
+ ### 2. Build an EVM sign request
31
36
 
32
37
  Encode a message as animated UR parts to display as a QR code for the wallet to scan:
33
38
 
34
39
  ```ts
35
- import { buildEthSignRequestURParts, buildEthSignRequestUR, EthDataType } from '@qrkit/core'
40
+ import {
41
+ buildEthSignRequestURParts,
42
+ buildEthSignRequestUR,
43
+ EthDataType,
44
+ } from "@qrkit/core";
36
45
 
37
46
  // Animated QR (multiple parts for long messages)
38
47
  const parts = buildEthSignRequestURParts({
39
- signData: message, // string (UTF-8 encoded) or Uint8Array (raw bytes)
48
+ signData: message, // string (UTF-8 encoded) or Uint8Array (raw bytes)
40
49
  dataType: EthDataType.PersonalMessage, // defaults to PersonalMessage if omitted
41
- address: account.address,
42
- sourceFingerprint: account.sourceFingerprint,
43
- })
50
+ address: evm.address,
51
+ sourceFingerprint: evm.sourceFingerprint,
52
+ });
44
53
  // parts is string[] — cycle through them to animate the QR
45
54
 
46
55
  // Single-frame QR (short messages)
47
56
  const ur = buildEthSignRequestUR({
48
57
  signData: message,
49
- address: account.address,
50
- sourceFingerprint: account.sourceFingerprint,
51
- })
58
+ address: evm.address,
59
+ sourceFingerprint: evm.sourceFingerprint,
60
+ });
52
61
  ```
53
62
 
54
63
  ### 3. Parse the wallet's signature response
@@ -56,21 +65,72 @@ const ur = buildEthSignRequestUR({
56
65
  After the user scans the wallet's response QR, decode the signature:
57
66
 
58
67
  ```ts
59
- import { parseEthSignature } from '@qrkit/core'
68
+ import { parseEthSignature } from "@qrkit/core";
60
69
 
61
- const signature = parseEthSignature(scannedResponseUR)
70
+ const signature = parseEthSignature(scannedResponseUR);
62
71
  // → '0x...' hex string, ready for ethers / viem
63
72
  ```
64
73
 
74
+ ### 4. Build a BTC message sign request
75
+
76
+ Direct BTC message signing uses `btc-sign-request` and returns `btc-signature`:
77
+
78
+ ```ts
79
+ import { buildBtcSignRequestURParts, parseBtcSignature } from "@qrkit/core";
80
+
81
+ const parts = buildBtcSignRequestURParts({
82
+ signData: "Hello Bitcoin",
83
+ address: btc.address,
84
+ scriptType: btc.scriptType,
85
+ sourceFingerprint: btc.sourceFingerprint,
86
+ });
87
+
88
+ const result = parseBtcSignature(scannedResponseUR);
89
+ result.signature; // base64 compact Bitcoin message signature (65 bytes)
90
+ result.publicKey; // compressed public key hex
91
+ ```
92
+
93
+ ### 5. Carry a BTC PSBT
94
+
95
+ Bitcoin transaction signing, and BIP-322-style message signing, use `crypto-psbt`.
96
+ qrkit carries PSBT bytes over QR; the airgapped wallet signs offline and returns a signed `crypto-psbt`.
97
+
98
+ ```ts
99
+ import { buildCryptoPsbtURParts, parseCryptoPsbt } from "@qrkit/core";
100
+
101
+ const parts = buildCryptoPsbtURParts(unsignedPsbtHex);
102
+ // render parts as QR codes
103
+
104
+ const signed = parseCryptoPsbt(scannedResponseUR);
105
+ signed.psbtHex; // signed PSBT hex
106
+ ```
107
+
108
+ Note: BIP-322-style message signing is also carried as `crypto-psbt`, but wallet review UX varies. Some wallets present it as a Bitcoin message flow, while others show only the proving address or a generic PSBT review.
109
+
65
110
  ## API
66
111
 
67
- | Export | Description |
68
- |---|---|
69
- | `parseConnection(ur, options)` | Parse a `crypto-hdkey` or `crypto-account` UR into `Account[]` |
70
- | `buildEthSignRequestURParts(params)` | Build animated UR parts for a sign request (`EthSignRequestParams`) |
71
- | `buildEthSignRequestUR(params)` | Build a single-frame UR for a sign request (`EthSignRequestParams`) |
72
- | `EthDataType` | Constants for ERC-4527 data types (1–4) |
73
- | `parseEthSignature(ur)` | Decode an `eth-signature` UR into a `0x...` hex string |
112
+ | Export | Description |
113
+ | ------------------------------------ | ------------------------------------------------------------------------------- |
114
+ | `parseConnection(ur, options)` | Parse a connection UR into `Account[]` |
115
+ | `buildEthSignRequestURParts(params)` | Build animated UR parts for a sign request (`EthSignRequestParams`) |
116
+ | `buildEthSignRequestUR(params)` | Build a single-frame UR for a sign request (`EthSignRequestParams`) |
117
+ | `EthDataType` | Constants for ERC-4527 data types (1–4) |
118
+ | `parseEthSignature(ur)` | Decode an `eth-signature` UR into a `0x...` hex string |
119
+ | `buildBtcSignRequestURParts(params)` | Build animated UR parts for a BTC message sign request (`BtcSignRequestParams`) |
120
+ | `buildBtcSignRequestUR(params)` | Build a single-frame BTC message sign request UR |
121
+ | `BtcDataType` | Constants for BTC sign request data types |
122
+ | `parseBtcSignature(ur)` | Decode a `btc-signature` UR into a base64 signature and public key |
123
+ | `buildCryptoPsbtURParts(psbt)` | Build animated UR parts for a `crypto-psbt` request |
124
+ | `buildCryptoPsbtUR(psbt)` | Build a single-frame `crypto-psbt` UR |
125
+ | `parseCryptoPsbt(ur)` | Decode a `crypto-psbt` UR into PSBT bytes and hex |
126
+
127
+ ## Examples
128
+
129
+ ```sh
130
+ pnpm --filter @qrkit/core example:eth
131
+ pnpm --filter @qrkit/core example:btc-message
132
+ pnpm --filter @qrkit/core example:btc-psbt
133
+ ```
74
134
 
75
135
  ## License
76
136
 
package/dist/index.cjs CHANGED
@@ -30,14 +30,150 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ BtcDataType: () => BtcDataType,
33
34
  EthDataType: () => EthDataType,
35
+ buildBtcSignRequestUR: () => buildBtcSignRequestUR,
36
+ buildBtcSignRequestURParts: () => buildBtcSignRequestURParts,
37
+ buildCryptoPsbtUR: () => buildCryptoPsbtUR,
38
+ buildCryptoPsbtURParts: () => buildCryptoPsbtURParts,
34
39
  buildEthSignRequestUR: () => buildEthSignRequestUR,
35
40
  buildEthSignRequestURParts: () => buildEthSignRequestURParts,
41
+ parseBtcSignature: () => parseBtcSignature,
36
42
  parseConnection: () => parseConnection,
43
+ parseCryptoPsbt: () => parseCryptoPsbt,
37
44
  parseEthSignature: () => parseEthSignature
38
45
  });
39
46
  module.exports = __toCommonJS(index_exports);
40
47
 
48
+ // src/bytes.ts
49
+ function bytesToBase64(bytes) {
50
+ let binary = "";
51
+ for (const byte of bytes) {
52
+ binary += String.fromCharCode(byte);
53
+ }
54
+ return btoa(binary);
55
+ }
56
+ function bytesToHex(bytes) {
57
+ return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
58
+ }
59
+ function hexToBytes(hex) {
60
+ const normalized = hex.trim().replace(/^0x/i, "");
61
+ if (normalized.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(normalized)) {
62
+ throw new Error("Invalid hex string");
63
+ }
64
+ return new Uint8Array(
65
+ normalized.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []
66
+ );
67
+ }
68
+
69
+ // src/btc/address.ts
70
+ var import_base = require("@scure/base");
71
+ var import_legacy = require("@noble/hashes/legacy.js");
72
+ var import_sha2 = require("@noble/hashes/sha2.js");
73
+ var base58check = (0, import_base.createBase58check)(import_sha2.sha256);
74
+ function hash160(bytes) {
75
+ return (0, import_legacy.ripemd160)((0, import_sha2.sha256)(bytes));
76
+ }
77
+ function pubKeyToP2wpkh(compressedPubKey) {
78
+ const h = hash160(compressedPubKey);
79
+ const words = import_base.bech32.toWords(h);
80
+ return import_base.bech32.encode("bc", [0, ...words]);
81
+ }
82
+ function pubKeyToP2shP2wpkh(compressedPubKey) {
83
+ const h = hash160(compressedPubKey);
84
+ const redeemScript = new Uint8Array(22);
85
+ redeemScript[0] = 0;
86
+ redeemScript[1] = 20;
87
+ redeemScript.set(h, 2);
88
+ const scriptHash = hash160(redeemScript);
89
+ const payload = new Uint8Array(21);
90
+ payload[0] = 5;
91
+ payload.set(scriptHash, 1);
92
+ return base58check.encode(payload);
93
+ }
94
+ function pubKeyToP2pkh(compressedPubKey) {
95
+ const h = hash160(compressedPubKey);
96
+ const payload = new Uint8Array(21);
97
+ payload[0] = 0;
98
+ payload.set(h, 1);
99
+ return base58check.encode(payload);
100
+ }
101
+
102
+ // src/btc/deriveAccount.ts
103
+ function firstChild(accountKey) {
104
+ return accountKey.deriveChild(0).deriveChild(0);
105
+ }
106
+ function scriptTypeFromPurpose(purpose) {
107
+ if (purpose === 84) return "p2wpkh";
108
+ if (purpose === 49) return "p2sh-p2wpkh";
109
+ if (purpose === 44) return "p2pkh";
110
+ return void 0;
111
+ }
112
+ function deriveAddress(pubKey, scriptType) {
113
+ if (scriptType === "p2wpkh") return pubKeyToP2wpkh(pubKey);
114
+ if (scriptType === "p2sh-p2wpkh") return pubKeyToP2shP2wpkh(pubKey);
115
+ return pubKeyToP2pkh(pubKey);
116
+ }
117
+ function deriveBtcAccount(parsed) {
118
+ const results = [];
119
+ for (const entry of parsed) {
120
+ const { hdKey, purpose, coinType, sourceFingerprint, name } = entry;
121
+ if (coinType !== 0) continue;
122
+ const scriptType = scriptTypeFromPurpose(purpose);
123
+ if (!scriptType) continue;
124
+ const child = firstChild(hdKey);
125
+ if (!child.publicKey) continue;
126
+ results.push({
127
+ address: deriveAddress(child.publicKey, scriptType),
128
+ scriptType,
129
+ publicKey: bytesToHex(child.publicKey),
130
+ sourceFingerprint,
131
+ device: name
132
+ });
133
+ }
134
+ return results;
135
+ }
136
+
137
+ // src/eth/address.ts
138
+ var import_sha3 = require("@noble/hashes/sha3.js");
139
+ var secp = __toESM(require("@noble/secp256k1"), 1);
140
+ function pubKeyToEthAddress(compressedPubKey) {
141
+ const uncompressed = secp.Point.fromBytes(compressedPubKey).toBytes(false);
142
+ const hash = (0, import_sha3.keccak_256)(uncompressed.slice(1));
143
+ const hex = [...hash.slice(12)].map((b) => b.toString(16).padStart(2, "0")).join("");
144
+ return toChecksumAddress(hex);
145
+ }
146
+ function toChecksumAddress(hex) {
147
+ const checksumHash = (0, import_sha3.keccak_256)(new TextEncoder().encode(hex));
148
+ return "0x" + [...hex].map((c, i) => {
149
+ if (c >= "0" && c <= "9") return c;
150
+ const nibble = i % 2 === 0 ? checksumHash[Math.floor(i / 2)] >> 4 & 15 : checksumHash[Math.floor(i / 2)] & 15;
151
+ return nibble >= 8 ? c.toUpperCase() : c.toLowerCase();
152
+ }).join("");
153
+ }
154
+
155
+ // src/eth/deriveAccount.ts
156
+ function firstChild2(accountKey) {
157
+ return accountKey.deriveChild(0).deriveChild(0);
158
+ }
159
+ function deriveEvmAccount(parsed) {
160
+ const results = [];
161
+ for (const entry of parsed) {
162
+ const { hdKey, purpose, coinType, type, sourceFingerprint, name } = entry;
163
+ const isEvm = purpose === 44 && coinType === 60 || purpose === void 0 && type === "xpub";
164
+ if (!isEvm) continue;
165
+ const child = firstChild2(hdKey);
166
+ if (!child.publicKey) continue;
167
+ results.push({
168
+ address: pubKeyToEthAddress(child.publicKey),
169
+ publicKey: bytesToHex(child.publicKey),
170
+ sourceFingerprint,
171
+ device: name
172
+ });
173
+ }
174
+ return results;
175
+ }
176
+
41
177
  // src/parseXpub.ts
42
178
  var import_bip32 = require("@scure/bip32");
43
179
  var import_cborg = require("cborg");
@@ -45,23 +181,46 @@ function get(m, k) {
45
181
  if (m instanceof Map) return m.get(k);
46
182
  return void 0;
47
183
  }
184
+ function bytesFromCbor(value) {
185
+ if (value instanceof Uint8Array) return value;
186
+ if (value instanceof Map && value.get("type") === "Buffer") {
187
+ const data = value.get("data");
188
+ if (Array.isArray(data) && data.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
189
+ return new Uint8Array(data);
190
+ }
191
+ }
192
+ return void 0;
193
+ }
48
194
  var passthrough = (v) => v;
49
195
  function decodeCbor(cbor) {
196
+ const tags = new Proxy([], {
197
+ get(target, property, receiver) {
198
+ if (typeof property === "string" && /^\d+$/.test(property)) {
199
+ return passthrough;
200
+ }
201
+ return Reflect.get(target, property, receiver);
202
+ }
203
+ });
50
204
  return (0, import_cborg.decode)(cbor, {
51
205
  useMaps: true,
52
- tags: Object.assign([], {
53
- 303: passthrough,
54
- // crypto-hdkey
55
- 304: passthrough,
56
- // crypto-keypath
57
- 305: passthrough
58
- // crypto-coin-info
59
- })
206
+ tags
60
207
  });
61
208
  }
62
- function parseCryptoHdKey(map, raw) {
63
- const keyData = get(map, 3);
64
- const chainCode = get(map, 4);
209
+ function isCborMap(value) {
210
+ return value instanceof Map;
211
+ }
212
+ function assertCryptoHdKeyShape(value) {
213
+ if (!isCborMap(value)) {
214
+ throw new Error("crypto-hdkey entry must be a CBOR map");
215
+ }
216
+ if (!value.has(3) || !value.has(4)) {
217
+ throw new Error("crypto-hdkey missing key-data or chain-code");
218
+ }
219
+ return value;
220
+ }
221
+ function parseCryptoHdKey(map, raw, fallbackName) {
222
+ const keyData = bytesFromCbor(get(map, 3));
223
+ const chainCode = bytesFromCbor(get(map, 4));
65
224
  if (!keyData || !chainCode) {
66
225
  throw new Error("crypto-hdkey missing key-data or chain-code");
67
226
  }
@@ -77,25 +236,28 @@ function parseCryptoHdKey(map, raw) {
77
236
  }
78
237
  sourceFingerprint = get(origin, 2);
79
238
  }
80
- const name = get(map, 9);
239
+ const name = get(map, 9) ?? fallbackName;
81
240
  const hdKey = new import_bip32.HDKey({ publicKey: keyData, chainCode });
82
241
  return { hdKey, type: "xpub", purpose, coinType, sourceFingerprint, name, raw };
83
242
  }
84
243
  function parseScannedUR(scanned) {
85
244
  const { type, cbor } = scanned;
86
245
  const raw = `ur:${type}`;
87
- if (type !== "crypto-hdkey" && type !== "crypto-account") {
246
+ if (type !== "crypto-hdkey" && type !== "crypto-account" && type !== "crypto-multi-accounts") {
88
247
  throw new Error(`Unsupported UR type: ${type}`);
89
248
  }
90
249
  const map = decodeCbor(cbor);
91
250
  if (type === "crypto-hdkey") {
92
- return parseCryptoHdKey(map, raw);
251
+ return parseCryptoHdKey(assertCryptoHdKeyShape(map), raw);
93
252
  }
94
253
  const accounts = map.get(2);
95
254
  if (!Array.isArray(accounts) || accounts.length === 0) {
96
- throw new Error("crypto-account contains no keys");
255
+ throw new Error(`${type} contains no keys`);
97
256
  }
98
- return accounts.map((entry) => parseCryptoHdKey(entry, raw));
257
+ const fallbackName = type === "crypto-multi-accounts" ? map.get(3) : void 0;
258
+ return accounts.map(
259
+ (entry) => parseCryptoHdKey(assertCryptoHdKeyShape(entry), raw, fallbackName)
260
+ );
99
261
  }
100
262
  function parseXpub(input) {
101
263
  if (typeof input !== "string") {
@@ -115,48 +277,6 @@ function parseXpub(input) {
115
277
  ];
116
278
  }
117
279
 
118
- // src/eth/address.ts
119
- var import_sha3 = require("@noble/hashes/sha3.js");
120
- var secp = __toESM(require("@noble/secp256k1"), 1);
121
- function pubKeyToEthAddress(compressedPubKey) {
122
- const uncompressed = secp.Point.fromBytes(compressedPubKey).toBytes(false);
123
- const hash = (0, import_sha3.keccak_256)(uncompressed.slice(1));
124
- const hex = [...hash.slice(12)].map((b) => b.toString(16).padStart(2, "0")).join("");
125
- return toChecksumAddress(hex);
126
- }
127
- function toChecksumAddress(hex) {
128
- const checksumHash = (0, import_sha3.keccak_256)(new TextEncoder().encode(hex));
129
- return "0x" + [...hex].map((c, i) => {
130
- if (c >= "0" && c <= "9") return c;
131
- const nibble = i % 2 === 0 ? checksumHash[Math.floor(i / 2)] >> 4 & 15 : checksumHash[Math.floor(i / 2)] & 15;
132
- return nibble >= 8 ? c.toUpperCase() : c.toLowerCase();
133
- }).join("");
134
- }
135
-
136
- // src/eth/deriveAccount.ts
137
- function firstChild(accountKey) {
138
- return accountKey.deriveChild(0).deriveChild(0);
139
- }
140
- function toHex(bytes) {
141
- return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
142
- }
143
- function deriveEvmAccount(parsed) {
144
- for (const entry of parsed) {
145
- const { hdKey, purpose, coinType, type, sourceFingerprint, name } = entry;
146
- const isEvm = purpose === 44 && coinType === 60 || purpose === void 0 && type === "xpub";
147
- if (!isEvm) continue;
148
- const child = firstChild(hdKey);
149
- if (!child.publicKey) continue;
150
- return {
151
- address: pubKeyToEthAddress(child.publicKey),
152
- publicKey: toHex(child.publicKey),
153
- sourceFingerprint,
154
- device: name
155
- };
156
- }
157
- return void 0;
158
- }
159
-
160
280
  // src/parseConnection.ts
161
281
  var ALL_CHAINS = ["evm", "btc"];
162
282
  function parseConnection(scannedUR, config = {}) {
@@ -164,11 +284,15 @@ function parseConnection(scannedUR, config = {}) {
164
284
  const parsed = parseXpub(scannedUR);
165
285
  const accounts = [];
166
286
  if (chains.includes("evm")) {
167
- const account = deriveEvmAccount(parsed);
168
- if (account) {
287
+ for (const account of deriveEvmAccount(parsed)) {
169
288
  accounts.push({ chain: "evm", ...account });
170
289
  }
171
290
  }
291
+ if (chains.includes("btc")) {
292
+ for (const account of deriveBtcAccount(parsed)) {
293
+ accounts.push({ chain: "btc", ...account });
294
+ }
295
+ }
172
296
  return accounts;
173
297
  }
174
298
 
@@ -222,10 +346,10 @@ function encode(value) {
222
346
  }
223
347
 
224
348
  // src/urEncoding.ts
225
- var import_bc_ur = require("@qrkit/bc-ur");
349
+ var import_bc_ur_web = require("@qrkit/bc-ur-web");
226
350
  function encodeURParts(cbor, type, maxFragmentLength = 200) {
227
- const ur = import_bc_ur.UR.fromCbor({ type, payload: cbor });
228
- const encoder = new import_bc_ur.UrFountainEncoder(ur, maxFragmentLength);
351
+ const ur = import_bc_ur_web.UR.fromCbor({ type, payload: cbor });
352
+ const encoder = new import_bc_ur_web.UrFountainEncoder(ur, maxFragmentLength);
229
353
  return encoder.getAllPartsUr().map((part) => part.toString().toUpperCase());
230
354
  }
231
355
 
@@ -305,14 +429,132 @@ function parseEthSignature(scanned) {
305
429
  if (!sigBytes || sigBytes.length < 64) {
306
430
  throw new Error("Invalid or missing signature bytes");
307
431
  }
308
- return "0x" + [...sigBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
432
+ return `0x${bytesToHex(sigBytes)}`;
433
+ }
434
+
435
+ // src/btc/signRequest.ts
436
+ var BtcDataType = {
437
+ Message: 1
438
+ };
439
+ var TAG_KEYPATH2 = 304;
440
+ var PURPOSE_BY_SCRIPT_TYPE = {
441
+ p2wpkh: 84,
442
+ "p2sh-p2wpkh": 49,
443
+ p2pkh: 44
444
+ };
445
+ function randomBytes2(n) {
446
+ const buf = new Uint8Array(n);
447
+ crypto.getRandomValues(buf);
448
+ return buf;
449
+ }
450
+ function purposeFromScriptType(scriptType) {
451
+ return PURPOSE_BY_SCRIPT_TYPE[scriptType];
452
+ }
453
+ function buildKeypath2(scriptType, sourceFingerprint) {
454
+ const purpose = purposeFromScriptType(scriptType);
455
+ const components = [purpose, true, 0, true, 0, true, 0, false, 0, false];
456
+ const keypathMap = /* @__PURE__ */ new Map([[1, components]]);
457
+ if (sourceFingerprint !== void 0) {
458
+ keypathMap.set(2, sourceFingerprint);
459
+ }
460
+ return new CborTag(TAG_KEYPATH2, keypathMap);
461
+ }
462
+ function buildBtcSignRequestCbor(params) {
463
+ const { signData, address, scriptType, sourceFingerprint, origin = "qrkit" } = params;
464
+ const requestId = randomBytes2(16);
465
+ const signBytes = typeof signData === "string" ? new TextEncoder().encode(signData) : signData;
466
+ const keypath = buildKeypath2(scriptType, sourceFingerprint);
467
+ return encode(
468
+ /* @__PURE__ */ new Map([
469
+ [1, new CborTag(37, requestId)],
470
+ // request-id: uuid = #6.37(bstr)
471
+ [2, signBytes],
472
+ // sign-data
473
+ [3, BtcDataType.Message],
474
+ // data-type
475
+ [4, [keypath]],
476
+ // btc-derivation-paths
477
+ [5, [address]],
478
+ // btc-addresses
479
+ [6, origin]
480
+ // origin
481
+ ])
482
+ );
483
+ }
484
+ function buildBtcSignRequestURParts(params) {
485
+ return encodeURParts(buildBtcSignRequestCbor(params), "btc-sign-request");
486
+ }
487
+ function buildBtcSignRequestUR(params) {
488
+ return encodeURParts(buildBtcSignRequestCbor(params), "btc-sign-request")[0];
489
+ }
490
+
491
+ // src/btc/signature.ts
492
+ var import_cborg3 = require("cborg");
493
+ function parseBtcSignature(scanned) {
494
+ if (scanned.type !== "btc-signature") {
495
+ throw new Error(`Expected btc-signature, got: ${scanned.type}`);
496
+ }
497
+ const map = (0, import_cborg3.decode)(scanned.cbor, {
498
+ useMaps: true,
499
+ tags: Object.assign([], { 37: (v) => v })
500
+ });
501
+ const requestId = map.get(1);
502
+ const sigBytes = map.get(2);
503
+ const publicKeyBytes = map.get(3);
504
+ if (!sigBytes || sigBytes.length !== 65) {
505
+ throw new Error("Invalid or missing BTC signature bytes");
506
+ }
507
+ if (!publicKeyBytes || publicKeyBytes.length !== 33) {
508
+ throw new Error("Invalid or missing BTC public key bytes");
509
+ }
510
+ return {
511
+ signature: bytesToBase64(sigBytes),
512
+ publicKey: bytesToHex(publicKeyBytes),
513
+ requestId: requestId ? bytesToHex(requestId) : void 0
514
+ };
515
+ }
516
+
517
+ // src/btc/psbt.ts
518
+ var import_cborg4 = require("cborg");
519
+ function normalizePsbt(psbt) {
520
+ try {
521
+ return typeof psbt === "string" ? hexToBytes(psbt) : psbt;
522
+ } catch (error) {
523
+ throw new Error("Invalid PSBT hex", { cause: error });
524
+ }
525
+ }
526
+ function parseCryptoPsbt(scanned) {
527
+ if (scanned.type !== "crypto-psbt") {
528
+ throw new Error(`Expected crypto-psbt, got: ${scanned.type}`);
529
+ }
530
+ const psbt = (0, import_cborg4.decode)(scanned.cbor);
531
+ if (!(psbt instanceof Uint8Array)) {
532
+ throw new Error("Invalid crypto-psbt payload");
533
+ }
534
+ return {
535
+ psbt,
536
+ psbtHex: bytesToHex(psbt)
537
+ };
538
+ }
539
+ function buildCryptoPsbtURParts(psbt) {
540
+ return encodeURParts(encode(normalizePsbt(psbt)), "crypto-psbt");
541
+ }
542
+ function buildCryptoPsbtUR(psbt) {
543
+ return buildCryptoPsbtURParts(psbt)[0];
309
544
  }
310
545
  // Annotate the CommonJS export names for ESM import in node:
311
546
  0 && (module.exports = {
547
+ BtcDataType,
312
548
  EthDataType,
549
+ buildBtcSignRequestUR,
550
+ buildBtcSignRequestURParts,
551
+ buildCryptoPsbtUR,
552
+ buildCryptoPsbtURParts,
313
553
  buildEthSignRequestUR,
314
554
  buildEthSignRequestURParts,
555
+ parseBtcSignature,
315
556
  parseConnection,
557
+ parseCryptoPsbt,
316
558
  parseEthSignature
317
559
  });
318
560
  //# sourceMappingURL=index.cjs.map