@qrkit/core 0.3.2 → 0.4.1

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
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,27 +15,156 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
31
21
  var index_exports = {};
32
22
  __export(index_exports, {
23
+ BtcDataType: () => BtcDataType,
33
24
  EthDataType: () => EthDataType,
25
+ buildBtcSignRequestUR: () => buildBtcSignRequestUR,
26
+ buildBtcSignRequestURParts: () => buildBtcSignRequestURParts,
27
+ buildCryptoPsbtUR: () => buildCryptoPsbtUR,
28
+ buildCryptoPsbtURParts: () => buildCryptoPsbtURParts,
34
29
  buildEthSignRequestUR: () => buildEthSignRequestUR,
35
30
  buildEthSignRequestURParts: () => buildEthSignRequestURParts,
31
+ parseBtcSignature: () => parseBtcSignature,
36
32
  parseConnection: () => parseConnection,
33
+ parseCryptoPsbt: () => parseCryptoPsbt,
37
34
  parseEthSignature: () => parseEthSignature
38
35
  });
39
36
  module.exports = __toCommonJS(index_exports);
40
37
 
38
+ // src/btc/address.ts
39
+ var import_base = require("@scure/base");
40
+ var import_legacy = require("@noble/hashes/legacy.js");
41
+ var import_sha2 = require("@noble/hashes/sha2.js");
42
+ var base58check = (0, import_base.createBase58check)(import_sha2.sha256);
43
+ function hash160(bytes) {
44
+ return (0, import_legacy.ripemd160)((0, import_sha2.sha256)(bytes));
45
+ }
46
+ function pubKeyToP2wpkh(compressedPubKey) {
47
+ const h = hash160(compressedPubKey);
48
+ const words = import_base.bech32.toWords(h);
49
+ return import_base.bech32.encode("bc", [0, ...words]);
50
+ }
51
+ function pubKeyToP2shP2wpkh(compressedPubKey) {
52
+ const h = hash160(compressedPubKey);
53
+ const redeemScript = new Uint8Array(22);
54
+ redeemScript[0] = 0;
55
+ redeemScript[1] = 20;
56
+ redeemScript.set(h, 2);
57
+ const scriptHash = hash160(redeemScript);
58
+ const payload = new Uint8Array(21);
59
+ payload[0] = 5;
60
+ payload.set(scriptHash, 1);
61
+ return base58check.encode(payload);
62
+ }
63
+ function pubKeyToP2pkh(compressedPubKey) {
64
+ const h = hash160(compressedPubKey);
65
+ const payload = new Uint8Array(21);
66
+ payload[0] = 0;
67
+ payload.set(h, 1);
68
+ return base58check.encode(payload);
69
+ }
70
+
71
+ // src/bytes.ts
72
+ function bytesToBase64(bytes) {
73
+ let binary = "";
74
+ for (const byte of bytes) {
75
+ binary += String.fromCharCode(byte);
76
+ }
77
+ return btoa(binary);
78
+ }
79
+ function bytesToHex(bytes) {
80
+ return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
81
+ }
82
+ function hexToBytes(hex) {
83
+ const normalized = hex.trim().replace(/^0x/i, "");
84
+ if (normalized.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(normalized)) {
85
+ throw new Error("Invalid hex string");
86
+ }
87
+ return new Uint8Array(
88
+ normalized.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []
89
+ );
90
+ }
91
+
92
+ // src/btc/deriveAccount.ts
93
+ function firstChild(accountKey) {
94
+ return accountKey.deriveChild(0).deriveChild(0);
95
+ }
96
+ function scriptTypeFromPurpose(purpose) {
97
+ if (purpose === 84) return "p2wpkh";
98
+ if (purpose === 49) return "p2sh-p2wpkh";
99
+ if (purpose === 44) return "p2pkh";
100
+ return void 0;
101
+ }
102
+ function deriveAddress(pubKey, scriptType) {
103
+ if (scriptType === "p2wpkh") return pubKeyToP2wpkh(pubKey);
104
+ if (scriptType === "p2sh-p2wpkh") return pubKeyToP2shP2wpkh(pubKey);
105
+ return pubKeyToP2pkh(pubKey);
106
+ }
107
+ function deriveBtcAccount(parsed) {
108
+ const results = [];
109
+ for (const entry of parsed) {
110
+ const { hdKey, purpose, coinType, sourceFingerprint, name } = entry;
111
+ if (coinType !== 0) continue;
112
+ const scriptType = scriptTypeFromPurpose(purpose);
113
+ if (!scriptType) continue;
114
+ const child = firstChild(hdKey);
115
+ if (!child.publicKey) continue;
116
+ results.push({
117
+ address: deriveAddress(child.publicKey, scriptType),
118
+ scriptType,
119
+ publicKey: bytesToHex(child.publicKey),
120
+ sourceFingerprint,
121
+ device: name
122
+ });
123
+ }
124
+ return results;
125
+ }
126
+
127
+ // src/eth/address.ts
128
+ var import_secp256k1 = require("@noble/curves/secp256k1.js");
129
+ var import_sha3 = require("@noble/hashes/sha3.js");
130
+ function pubKeyToEthAddress(compressedPubKey) {
131
+ const pubKeyHex = [...compressedPubKey].map((b) => b.toString(16).padStart(2, "0")).join("");
132
+ const uncompressed = import_secp256k1.secp256k1.Point.fromHex(pubKeyHex).toBytes(false);
133
+ const hash = (0, import_sha3.keccak_256)(uncompressed.slice(1));
134
+ const hex = [...hash.slice(12)].map((b) => b.toString(16).padStart(2, "0")).join("");
135
+ return toChecksumAddress(hex);
136
+ }
137
+ function toChecksumAddress(hex) {
138
+ const checksumHash = (0, import_sha3.keccak_256)(new TextEncoder().encode(hex));
139
+ return "0x" + [...hex].map((c, i) => {
140
+ if (c >= "0" && c <= "9") return c;
141
+ const nibble = i % 2 === 0 ? checksumHash[Math.floor(i / 2)] >> 4 & 15 : checksumHash[Math.floor(i / 2)] & 15;
142
+ return nibble >= 8 ? c.toUpperCase() : c.toLowerCase();
143
+ }).join("");
144
+ }
145
+
146
+ // src/eth/deriveAccount.ts
147
+ function firstChild2(accountKey) {
148
+ return accountKey.deriveChild(0).deriveChild(0);
149
+ }
150
+ function deriveEvmAccount(parsed) {
151
+ const results = [];
152
+ for (const entry of parsed) {
153
+ const { hdKey, purpose, coinType, type, sourceFingerprint, name } = entry;
154
+ const isEvm = purpose === 44 && coinType === 60 || purpose === void 0 && type === "xpub";
155
+ if (!isEvm) continue;
156
+ const child = firstChild2(hdKey);
157
+ if (!child.publicKey) continue;
158
+ results.push({
159
+ address: pubKeyToEthAddress(child.publicKey),
160
+ publicKey: bytesToHex(child.publicKey),
161
+ sourceFingerprint,
162
+ device: name
163
+ });
164
+ }
165
+ return results;
166
+ }
167
+
41
168
  // src/parseXpub.ts
42
169
  var import_bip32 = require("@scure/bip32");
43
170
  var import_cborg = require("cborg");
@@ -45,23 +172,46 @@ function get(m, k) {
45
172
  if (m instanceof Map) return m.get(k);
46
173
  return void 0;
47
174
  }
175
+ function bytesFromCbor(value) {
176
+ if (value instanceof Uint8Array) return value;
177
+ if (value instanceof Map && value.get("type") === "Buffer") {
178
+ const data = value.get("data");
179
+ if (Array.isArray(data) && data.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255)) {
180
+ return new Uint8Array(data);
181
+ }
182
+ }
183
+ return void 0;
184
+ }
48
185
  var passthrough = (v) => v;
49
186
  function decodeCbor(cbor) {
187
+ const tags = new Proxy([], {
188
+ get(target, property, receiver) {
189
+ if (typeof property === "string" && /^\d+$/.test(property)) {
190
+ return passthrough;
191
+ }
192
+ return Reflect.get(target, property, receiver);
193
+ }
194
+ });
50
195
  return (0, import_cborg.decode)(cbor, {
51
196
  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
- })
197
+ tags
60
198
  });
61
199
  }
62
- function parseCryptoHdKey(map, raw) {
63
- const keyData = get(map, 3);
64
- const chainCode = get(map, 4);
200
+ function isCborMap(value) {
201
+ return value instanceof Map;
202
+ }
203
+ function assertCryptoHdKeyShape(value) {
204
+ if (!isCborMap(value)) {
205
+ throw new Error("crypto-hdkey entry must be a CBOR map");
206
+ }
207
+ if (!value.has(3) || !value.has(4)) {
208
+ throw new Error("crypto-hdkey missing key-data or chain-code");
209
+ }
210
+ return value;
211
+ }
212
+ function parseCryptoHdKey(map, raw, fallbackName) {
213
+ const keyData = bytesFromCbor(get(map, 3));
214
+ const chainCode = bytesFromCbor(get(map, 4));
65
215
  if (!keyData || !chainCode) {
66
216
  throw new Error("crypto-hdkey missing key-data or chain-code");
67
217
  }
@@ -77,25 +227,28 @@ function parseCryptoHdKey(map, raw) {
77
227
  }
78
228
  sourceFingerprint = get(origin, 2);
79
229
  }
80
- const name = get(map, 9);
230
+ const name = get(map, 9) ?? fallbackName;
81
231
  const hdKey = new import_bip32.HDKey({ publicKey: keyData, chainCode });
82
232
  return { hdKey, type: "xpub", purpose, coinType, sourceFingerprint, name, raw };
83
233
  }
84
234
  function parseScannedUR(scanned) {
85
235
  const { type, cbor } = scanned;
86
236
  const raw = `ur:${type}`;
87
- if (type !== "crypto-hdkey" && type !== "crypto-account") {
237
+ if (type !== "crypto-hdkey" && type !== "crypto-account" && type !== "crypto-multi-accounts") {
88
238
  throw new Error(`Unsupported UR type: ${type}`);
89
239
  }
90
240
  const map = decodeCbor(cbor);
91
241
  if (type === "crypto-hdkey") {
92
- return parseCryptoHdKey(map, raw);
242
+ return parseCryptoHdKey(assertCryptoHdKeyShape(map), raw);
93
243
  }
94
244
  const accounts = map.get(2);
95
245
  if (!Array.isArray(accounts) || accounts.length === 0) {
96
- throw new Error("crypto-account contains no keys");
246
+ throw new Error(`${type} contains no keys`);
97
247
  }
98
- return accounts.map((entry) => parseCryptoHdKey(entry, raw));
248
+ const fallbackName = type === "crypto-multi-accounts" ? map.get(3) : void 0;
249
+ return accounts.map(
250
+ (entry) => parseCryptoHdKey(assertCryptoHdKeyShape(entry), raw, fallbackName)
251
+ );
99
252
  }
100
253
  function parseXpub(input) {
101
254
  if (typeof input !== "string") {
@@ -115,48 +268,6 @@ function parseXpub(input) {
115
268
  ];
116
269
  }
117
270
 
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
271
  // src/parseConnection.ts
161
272
  var ALL_CHAINS = ["evm", "btc"];
162
273
  function parseConnection(scannedUR, config = {}) {
@@ -164,11 +275,15 @@ function parseConnection(scannedUR, config = {}) {
164
275
  const parsed = parseXpub(scannedUR);
165
276
  const accounts = [];
166
277
  if (chains.includes("evm")) {
167
- const account = deriveEvmAccount(parsed);
168
- if (account) {
278
+ for (const account of deriveEvmAccount(parsed)) {
169
279
  accounts.push({ chain: "evm", ...account });
170
280
  }
171
281
  }
282
+ if (chains.includes("btc")) {
283
+ for (const account of deriveBtcAccount(parsed)) {
284
+ accounts.push({ chain: "btc", ...account });
285
+ }
286
+ }
172
287
  return accounts;
173
288
  }
174
289
 
@@ -305,14 +420,132 @@ function parseEthSignature(scanned) {
305
420
  if (!sigBytes || sigBytes.length < 64) {
306
421
  throw new Error("Invalid or missing signature bytes");
307
422
  }
308
- return "0x" + [...sigBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
423
+ return `0x${bytesToHex(sigBytes)}`;
424
+ }
425
+
426
+ // src/btc/signRequest.ts
427
+ var BtcDataType = {
428
+ Message: 1
429
+ };
430
+ var TAG_KEYPATH2 = 304;
431
+ var PURPOSE_BY_SCRIPT_TYPE = {
432
+ p2wpkh: 84,
433
+ "p2sh-p2wpkh": 49,
434
+ p2pkh: 44
435
+ };
436
+ function randomBytes2(n) {
437
+ const buf = new Uint8Array(n);
438
+ crypto.getRandomValues(buf);
439
+ return buf;
440
+ }
441
+ function purposeFromScriptType(scriptType) {
442
+ return PURPOSE_BY_SCRIPT_TYPE[scriptType];
443
+ }
444
+ function buildKeypath2(scriptType, sourceFingerprint) {
445
+ const purpose = purposeFromScriptType(scriptType);
446
+ const components = [purpose, true, 0, true, 0, true, 0, false, 0, false];
447
+ const keypathMap = /* @__PURE__ */ new Map([[1, components]]);
448
+ if (sourceFingerprint !== void 0) {
449
+ keypathMap.set(2, sourceFingerprint);
450
+ }
451
+ return new CborTag(TAG_KEYPATH2, keypathMap);
452
+ }
453
+ function buildBtcSignRequestCbor(params) {
454
+ const { signData, address, scriptType, sourceFingerprint, origin = "qrkit" } = params;
455
+ const requestId = randomBytes2(16);
456
+ const signBytes = typeof signData === "string" ? new TextEncoder().encode(signData) : signData;
457
+ const keypath = buildKeypath2(scriptType, sourceFingerprint);
458
+ return encode(
459
+ /* @__PURE__ */ new Map([
460
+ [1, new CborTag(37, requestId)],
461
+ // request-id: uuid = #6.37(bstr)
462
+ [2, signBytes],
463
+ // sign-data
464
+ [3, BtcDataType.Message],
465
+ // data-type
466
+ [4, [keypath]],
467
+ // btc-derivation-paths
468
+ [5, [address]],
469
+ // btc-addresses
470
+ [6, origin]
471
+ // origin
472
+ ])
473
+ );
474
+ }
475
+ function buildBtcSignRequestURParts(params) {
476
+ return encodeURParts(buildBtcSignRequestCbor(params), "btc-sign-request");
477
+ }
478
+ function buildBtcSignRequestUR(params) {
479
+ return encodeURParts(buildBtcSignRequestCbor(params), "btc-sign-request")[0];
480
+ }
481
+
482
+ // src/btc/signature.ts
483
+ var import_cborg3 = require("cborg");
484
+ function parseBtcSignature(scanned) {
485
+ if (scanned.type !== "btc-signature") {
486
+ throw new Error(`Expected btc-signature, got: ${scanned.type}`);
487
+ }
488
+ const map = (0, import_cborg3.decode)(scanned.cbor, {
489
+ useMaps: true,
490
+ tags: Object.assign([], { 37: (v) => v })
491
+ });
492
+ const requestId = map.get(1);
493
+ const sigBytes = map.get(2);
494
+ const publicKeyBytes = map.get(3);
495
+ if (!sigBytes || sigBytes.length !== 65) {
496
+ throw new Error("Invalid or missing BTC signature bytes");
497
+ }
498
+ if (!publicKeyBytes || publicKeyBytes.length !== 33) {
499
+ throw new Error("Invalid or missing BTC public key bytes");
500
+ }
501
+ return {
502
+ signature: bytesToBase64(sigBytes),
503
+ publicKey: bytesToHex(publicKeyBytes),
504
+ requestId: requestId ? bytesToHex(requestId) : void 0
505
+ };
506
+ }
507
+
508
+ // src/btc/psbt.ts
509
+ var import_cborg4 = require("cborg");
510
+ function normalizePsbt(psbt) {
511
+ try {
512
+ return typeof psbt === "string" ? hexToBytes(psbt) : psbt;
513
+ } catch (error) {
514
+ throw new Error("Invalid PSBT hex", { cause: error });
515
+ }
516
+ }
517
+ function parseCryptoPsbt(scanned) {
518
+ if (scanned.type !== "crypto-psbt") {
519
+ throw new Error(`Expected crypto-psbt, got: ${scanned.type}`);
520
+ }
521
+ const psbt = (0, import_cborg4.decode)(scanned.cbor);
522
+ if (!(psbt instanceof Uint8Array)) {
523
+ throw new Error("Invalid crypto-psbt payload");
524
+ }
525
+ return {
526
+ psbt,
527
+ psbtHex: bytesToHex(psbt)
528
+ };
529
+ }
530
+ function buildCryptoPsbtURParts(psbt) {
531
+ return encodeURParts(encode(normalizePsbt(psbt)), "crypto-psbt");
532
+ }
533
+ function buildCryptoPsbtUR(psbt) {
534
+ return buildCryptoPsbtURParts(psbt)[0];
309
535
  }
310
536
  // Annotate the CommonJS export names for ESM import in node:
311
537
  0 && (module.exports = {
538
+ BtcDataType,
312
539
  EthDataType,
540
+ buildBtcSignRequestUR,
541
+ buildBtcSignRequestURParts,
542
+ buildCryptoPsbtUR,
543
+ buildCryptoPsbtURParts,
313
544
  buildEthSignRequestUR,
314
545
  buildEthSignRequestURParts,
546
+ parseBtcSignature,
315
547
  parseConnection,
548
+ parseCryptoPsbt,
316
549
  parseEthSignature
317
550
  });
318
551
  //# sourceMappingURL=index.cjs.map