@qrkit/core 0.2.0 → 0.3.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 +15 -5
- package/dist/index.cjs +40 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -3
- package/dist/index.d.ts +29 -3
- package/dist/index.js +39 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,14 +32,23 @@ account.sourceFingerprint // master key fingerprint — required for signing
|
|
|
32
32
|
Encode a message as animated UR parts to display as a QR code for the wallet to scan:
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
|
-
import { buildEthSignRequestURParts, buildEthSignRequestUR } from '@qrkit/core'
|
|
35
|
+
import { buildEthSignRequestURParts, buildEthSignRequestUR, EthDataType } from '@qrkit/core'
|
|
36
36
|
|
|
37
37
|
// Animated QR (multiple parts for long messages)
|
|
38
|
-
const parts = buildEthSignRequestURParts(
|
|
38
|
+
const parts = buildEthSignRequestURParts({
|
|
39
|
+
signData: message, // string (UTF-8 encoded) or Uint8Array (raw bytes)
|
|
40
|
+
dataType: EthDataType.PersonalMessage, // defaults to PersonalMessage if omitted
|
|
41
|
+
address: account.address,
|
|
42
|
+
sourceFingerprint: account.sourceFingerprint,
|
|
43
|
+
})
|
|
39
44
|
// parts is string[] — cycle through them to animate the QR
|
|
40
45
|
|
|
41
46
|
// Single-frame QR (short messages)
|
|
42
|
-
const ur = buildEthSignRequestUR(
|
|
47
|
+
const ur = buildEthSignRequestUR({
|
|
48
|
+
signData: message,
|
|
49
|
+
address: account.address,
|
|
50
|
+
sourceFingerprint: account.sourceFingerprint,
|
|
51
|
+
})
|
|
43
52
|
```
|
|
44
53
|
|
|
45
54
|
### 3. Parse the wallet's signature response
|
|
@@ -58,8 +67,9 @@ const signature = parseEthSignature(scannedResponseUR)
|
|
|
58
67
|
| Export | Description |
|
|
59
68
|
|---|---|
|
|
60
69
|
| `parseConnection(ur, options)` | Parse a `crypto-hdkey` or `crypto-account` UR into `Account[]` |
|
|
61
|
-
| `buildEthSignRequestURParts(
|
|
62
|
-
| `buildEthSignRequestUR(
|
|
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) |
|
|
63
73
|
| `parseEthSignature(ur)` | Decode an `eth-signature` UR into a `0x...` hex string |
|
|
64
74
|
|
|
65
75
|
## License
|
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
EthDataType: () => EthDataType,
|
|
33
34
|
buildEthSignRequestUR: () => buildEthSignRequestUR,
|
|
34
35
|
buildEthSignRequestURParts: () => buildEthSignRequestURParts,
|
|
35
36
|
parseConnection: () => parseConnection,
|
|
@@ -76,8 +77,9 @@ function parseCryptoHdKey(map, raw) {
|
|
|
76
77
|
}
|
|
77
78
|
sourceFingerprint = get(origin, 2);
|
|
78
79
|
}
|
|
80
|
+
const name = get(map, 9);
|
|
79
81
|
const hdKey = new import_bip32.HDKey({ publicKey: keyData, chainCode });
|
|
80
|
-
return { hdKey, type: "xpub", purpose, coinType, sourceFingerprint, raw };
|
|
82
|
+
return { hdKey, type: "xpub", purpose, coinType, sourceFingerprint, name, raw };
|
|
81
83
|
}
|
|
82
84
|
function parseScannedUR(scanned) {
|
|
83
85
|
const { type, cbor } = scanned;
|
|
@@ -140,7 +142,7 @@ function toHex(bytes) {
|
|
|
140
142
|
}
|
|
141
143
|
function deriveEvmAccount(parsed) {
|
|
142
144
|
for (const entry of parsed) {
|
|
143
|
-
const { hdKey, purpose, coinType, type, sourceFingerprint } = entry;
|
|
145
|
+
const { hdKey, purpose, coinType, type, sourceFingerprint, name } = entry;
|
|
144
146
|
const isEvm = purpose === 44 && coinType === 60 || purpose === void 0 && type === "xpub";
|
|
145
147
|
if (!isEvm) continue;
|
|
146
148
|
const child = firstChild(hdKey);
|
|
@@ -148,7 +150,8 @@ function deriveEvmAccount(parsed) {
|
|
|
148
150
|
return {
|
|
149
151
|
address: pubKeyToEthAddress(child.publicKey),
|
|
150
152
|
publicKey: toHex(child.publicKey),
|
|
151
|
-
sourceFingerprint
|
|
153
|
+
sourceFingerprint,
|
|
154
|
+
device: name
|
|
152
155
|
};
|
|
153
156
|
}
|
|
154
157
|
return void 0;
|
|
@@ -227,7 +230,16 @@ function encodeURParts(cbor, type, maxFragmentLength = 200) {
|
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
// src/eth/signRequest.ts
|
|
230
|
-
var
|
|
233
|
+
var EthDataType = {
|
|
234
|
+
/** Legacy transaction (RLP-encoded). Wallet applies EIP-155: v = 35 + 2*chainId + recId */
|
|
235
|
+
LegacyTransaction: 1,
|
|
236
|
+
/** EIP-712 typed data bytes. Wallet hashes internally. */
|
|
237
|
+
TypedData: 2,
|
|
238
|
+
/** Personal message (EIP-191). Wallet prepends "\x19Ethereum Signed Message:\n{len}". */
|
|
239
|
+
PersonalMessage: 3,
|
|
240
|
+
/** EIP-1559 transaction (RLP-encoded). Wallet uses v = recId. */
|
|
241
|
+
TypedTransaction: 4
|
|
242
|
+
};
|
|
231
243
|
var TAG_KEYPATH = 304;
|
|
232
244
|
function randomBytes(n) {
|
|
233
245
|
const buf = new Uint8Array(n);
|
|
@@ -242,39 +254,41 @@ function buildKeypath(purpose, coinType, sourceFingerprint) {
|
|
|
242
254
|
}
|
|
243
255
|
return new CborTag(TAG_KEYPATH, keypathMap);
|
|
244
256
|
}
|
|
245
|
-
function buildEthSignRequestCbor(
|
|
257
|
+
function buildEthSignRequestCbor(params) {
|
|
258
|
+
const {
|
|
259
|
+
signData,
|
|
260
|
+
dataType = EthDataType.PersonalMessage,
|
|
261
|
+
address,
|
|
262
|
+
sourceFingerprint,
|
|
263
|
+
chainId,
|
|
264
|
+
origin = "qrkit"
|
|
265
|
+
} = params;
|
|
246
266
|
const requestId = randomBytes(16);
|
|
247
|
-
const
|
|
267
|
+
const signBytes = typeof signData === "string" ? new TextEncoder().encode(signData) : signData;
|
|
248
268
|
const keypath = buildKeypath(44, 60, sourceFingerprint);
|
|
249
269
|
const addrHex = address.replace(/^0x/i, "");
|
|
250
270
|
const addrBytes = new Uint8Array(addrHex.match(/.{2}/g).map((b) => parseInt(b, 16)));
|
|
251
271
|
const map = /* @__PURE__ */ new Map([
|
|
252
272
|
[1, new CborTag(37, requestId)],
|
|
253
273
|
// request-id: uuid = #6.37(bstr)
|
|
254
|
-
[2,
|
|
274
|
+
[2, signBytes],
|
|
255
275
|
// sign-data
|
|
256
|
-
[3,
|
|
257
|
-
// data-type
|
|
258
|
-
[5, keypath],
|
|
259
|
-
// derivation-path
|
|
260
|
-
[6, addrBytes],
|
|
261
|
-
// address
|
|
262
|
-
[7, origin]
|
|
263
|
-
// origin
|
|
276
|
+
[3, dataType]
|
|
277
|
+
// data-type
|
|
264
278
|
]);
|
|
279
|
+
if (chainId !== void 0) {
|
|
280
|
+
map.set(4, chainId);
|
|
281
|
+
}
|
|
282
|
+
map.set(5, keypath);
|
|
283
|
+
map.set(6, addrBytes);
|
|
284
|
+
map.set(7, origin);
|
|
265
285
|
return encode(map);
|
|
266
286
|
}
|
|
267
|
-
function buildEthSignRequestURParts(
|
|
268
|
-
return encodeURParts(
|
|
269
|
-
buildEthSignRequestCbor(message, address, sourceFingerprint, origin),
|
|
270
|
-
"eth-sign-request"
|
|
271
|
-
);
|
|
287
|
+
function buildEthSignRequestURParts(params) {
|
|
288
|
+
return encodeURParts(buildEthSignRequestCbor(params), "eth-sign-request");
|
|
272
289
|
}
|
|
273
|
-
function buildEthSignRequestUR(
|
|
274
|
-
return encodeURParts(
|
|
275
|
-
buildEthSignRequestCbor(message, address, sourceFingerprint, origin),
|
|
276
|
-
"eth-sign-request"
|
|
277
|
-
)[0];
|
|
290
|
+
function buildEthSignRequestUR(params) {
|
|
291
|
+
return encodeURParts(buildEthSignRequestCbor(params), "eth-sign-request")[0];
|
|
278
292
|
}
|
|
279
293
|
|
|
280
294
|
// src/eth/signature.ts
|
|
@@ -295,6 +309,7 @@ function parseEthSignature(scanned) {
|
|
|
295
309
|
}
|
|
296
310
|
// Annotate the CommonJS export names for ESM import in node:
|
|
297
311
|
0 && (module.exports = {
|
|
312
|
+
EthDataType,
|
|
298
313
|
buildEthSignRequestUR,
|
|
299
314
|
buildEthSignRequestURParts,
|
|
300
315
|
parseConnection,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/parseXpub.ts","../src/eth/address.ts","../src/eth/deriveAccount.ts","../src/parseConnection.ts","../src/cbor.ts","../src/urEncoding.ts","../src/eth/signRequest.ts","../src/eth/signature.ts"],"sourcesContent":["// Types\nexport type { ScannedUR, Chain, QRKitConfig, Account, EvmAccount } from \"./types.js\";\nexport type { ParsedXpub, XpubType } from \"./parseXpub.js\";\n\n// Connection\nexport { parseConnection } from \"./parseConnection.js\";\n\n// EVM signing\nexport { buildEthSignRequestUR, buildEthSignRequestURParts } from \"./eth/signRequest.js\";\nexport { parseEthSignature } from \"./eth/signature.js\";\n","import { HDKey } from \"@scure/bip32\";\nimport { decode as cborDecode, type TagDecoder } from \"cborg\";\n\nimport type { ScannedUR } from \"./types.js\";\n\nexport type XpubType = \"xpub\";\n\nexport interface ParsedXpub {\n hdKey: HDKey;\n type: XpubType;\n /** BIP-44 purpose index: 44 for EVM */\n purpose: number | undefined;\n /** BIP-44 coin type: 60 = ETH */\n coinType: number | undefined;\n /** source-fingerprint from the origin keypath — required by Shell for signing */\n sourceFingerprint: number | undefined;\n raw: string;\n}\n\nfunction get(m: unknown, k: number): unknown {\n if (m instanceof Map) return (m as Map<number, unknown>).get(k);\n return undefined;\n}\n\nconst passthrough = (v: unknown) => v;\n\nfunction decodeCbor(cbor: Uint8Array): Map<number, unknown> {\n return cborDecode(cbor, {\n useMaps: true,\n tags: Object.assign([] as TagDecoder[], {\n 303: passthrough, // crypto-hdkey\n 304: passthrough, // crypto-keypath\n 305: passthrough, // crypto-coin-info\n }),\n }) as Map<number, unknown>;\n}\n\nfunction parseCryptoHdKey(map: unknown, raw: string): ParsedXpub {\n const keyData = get(map, 3) as Uint8Array | undefined;\n const chainCode = get(map, 4) as Uint8Array | undefined;\n\n if (!keyData || !chainCode) {\n throw new Error(\"crypto-hdkey missing key-data or chain-code\");\n }\n\n let purpose: number | undefined;\n let coinType: number | undefined;\n let sourceFingerprint: number | undefined;\n const origin = get(map, 6);\n if (origin) {\n const components = get(origin, 1);\n if (Array.isArray(components)) {\n if (components.length >= 1) purpose = components[0] as number;\n if (components.length >= 3) coinType = components[2] as number;\n }\n sourceFingerprint = get(origin, 2) as number | undefined;\n }\n\n const hdKey = new HDKey({ publicKey: keyData, chainCode });\n return { hdKey, type: \"xpub\", purpose, coinType, sourceFingerprint, raw };\n}\n\nfunction parseScannedUR(scanned: ScannedUR): ParsedXpub | ParsedXpub[] {\n const { type, cbor } = scanned;\n const raw = `ur:${type}`;\n\n if (type !== \"crypto-hdkey\" && type !== \"crypto-account\") {\n throw new Error(`Unsupported UR type: ${type}`);\n }\n\n const map = decodeCbor(cbor);\n\n if (type === \"crypto-hdkey\") {\n return parseCryptoHdKey(map, raw);\n }\n\n const accounts = map.get(2) as unknown[] | undefined;\n if (!Array.isArray(accounts) || accounts.length === 0) {\n throw new Error(\"crypto-account contains no keys\");\n }\n return accounts.map((entry) => parseCryptoHdKey(entry, raw));\n}\n\nexport function parseXpub(input: ScannedUR | string): ParsedXpub[] {\n if (typeof input !== \"string\") {\n const result = parseScannedUR(input);\n return Array.isArray(result) ? result : [result];\n }\n // Raw base58 xpub string — @scure/bip32 handles decoding directly\n const hdKey = HDKey.fromExtendedKey(input.trim());\n return [\n {\n hdKey,\n type: \"xpub\",\n purpose: undefined,\n coinType: undefined,\n sourceFingerprint: undefined,\n raw: input.trim(),\n },\n ];\n}\n","import { keccak_256 } from \"@noble/hashes/sha3.js\";\nimport * as secp from \"@noble/secp256k1\";\n\nexport function pubKeyToEthAddress(compressedPubKey: Uint8Array): string {\n const uncompressed = secp.Point.fromBytes(compressedPubKey).toBytes(false);\n const hash = keccak_256(uncompressed.slice(1));\n const hex = [...hash.slice(12)]\n .map((b: number) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n return toChecksumAddress(hex);\n}\n\nfunction toChecksumAddress(hex: string): string {\n const checksumHash = keccak_256(new TextEncoder().encode(hex));\n return (\n \"0x\" +\n [...hex]\n .map((c, i) => {\n if (c >= \"0\" && c <= \"9\") return c;\n const nibble =\n i % 2 === 0\n ? (checksumHash[Math.floor(i / 2)] >> 4) & 0xf\n : checksumHash[Math.floor(i / 2)] & 0xf;\n return nibble >= 8 ? c.toUpperCase() : c.toLowerCase();\n })\n .join(\"\")\n );\n}\n","import type { HDKey } from \"@scure/bip32\";\n\nimport type { ParsedXpub } from \"../parseXpub.js\";\nimport { pubKeyToEthAddress } from \"./address.js\";\n\nexport interface DerivedAccount {\n address: string;\n publicKey: string;\n /** source-fingerprint from the scanned xpub — required by Shell for signing */\n sourceFingerprint: number | undefined;\n}\n\n// Derive the first external address (index 0) from an account-level xpub.\n// Account-level xpub is at depth 3 (m/purpose'/coin'/account').\n// External chain is child 0, then address index 0.\nfunction firstChild(accountKey: HDKey): HDKey {\n return accountKey.deriveChild(0).deriveChild(0);\n}\n\nfunction toHex(bytes: Uint8Array): string {\n return [...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function deriveEvmAccount(parsed: ParsedXpub[]): DerivedAccount | undefined {\n for (const entry of parsed) {\n const { hdKey, purpose, coinType, type, sourceFingerprint } = entry;\n const isEvm =\n (purpose === 44 && coinType === 60) || (purpose === undefined && type === \"xpub\");\n\n if (!isEvm) continue;\n\n const child = firstChild(hdKey);\n if (!child.publicKey) continue;\n\n return {\n address: pubKeyToEthAddress(child.publicKey),\n publicKey: toHex(child.publicKey),\n sourceFingerprint,\n };\n }\n return undefined;\n}\n","import { parseXpub } from \"./parseXpub.js\";\nimport { deriveEvmAccount } from \"./eth/deriveAccount.js\";\nimport type { Account, Chain, EvmAccount, QRKitConfig, ScannedUR } from \"./types.js\";\n\nconst ALL_CHAINS: Chain[] = [\"evm\", \"btc\"];\n\n/**\n * Parse a connection QR (crypto-hdkey or crypto-account) and return\n * only the accounts for the chains configured in QRKitConfig.\n *\n * A dApp configured with `chains: [\"evm\"]` will never see BTC accounts,\n * and vice versa. Both chains can be enabled with `chains: [\"evm\", \"btc\"]`.\n * If `chains` is omitted, all supported chains are tried.\n */\nexport function parseConnection(\n scannedUR: ScannedUR,\n config: QRKitConfig = {},\n): Account[] {\n const chains = config.chains ?? ALL_CHAINS;\n const parsed = parseXpub(scannedUR);\n const accounts: Account[] = [];\n\n if (chains.includes(\"evm\")) {\n const account = deriveEvmAccount(parsed);\n if (account) {\n accounts.push({ chain: \"evm\", ...account } satisfies EvmAccount);\n }\n }\n\n // \"btc\" — will be implemented in src/btc/ when Bitcoin support is added.\n\n return accounts;\n}\n","// Minimal CBOR encoder.\n// Supports only the types needed for eth-sign-request: uint, bytes, text, array, map (integer keys), bool, tag.\n\nfunction majorType(major: number, n: number): number[] {\n const base = major << 5;\n if (n <= 23) return [base | n];\n if (n <= 0xff) return [base | 0x18, n];\n if (n <= 0xffff) return [base | 0x19, (n >> 8) & 0xff, n & 0xff];\n return [base | 0x1a, (n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];\n}\n\nexport function encodeItem(value: unknown): number[] {\n if (typeof value === \"boolean\") {\n return [value ? 0xf5 : 0xf4];\n }\n if (typeof value === \"number\") {\n return majorType(0, value);\n }\n if (value instanceof Uint8Array) {\n return [...majorType(2, value.length), ...value];\n }\n if (typeof value === \"string\") {\n const bytes = new TextEncoder().encode(value);\n return [...majorType(3, bytes.length), ...bytes];\n }\n if (Array.isArray(value)) {\n return [...majorType(4, value.length), ...value.flatMap(encodeItem)];\n }\n if (value instanceof CborTag) {\n return [...majorType(6, value.tag), ...encodeItem(value.value)];\n }\n if (value instanceof Map) {\n const entries = [...value.entries()];\n return [\n ...majorType(5, entries.length),\n ...entries.flatMap(([k, v]) => [...encodeItem(k), ...encodeItem(v)]),\n ];\n }\n throw new Error(`Unsupported CBOR value: ${typeof value}`);\n}\n\nexport class CborTag {\n tag: number;\n value: unknown;\n constructor(tag: number, value: unknown) {\n this.tag = tag;\n this.value = value;\n }\n}\n\nexport function encode(value: unknown): Uint8Array {\n return new Uint8Array(encodeItem(value));\n}\n","import { UR, UrFountainEncoder } from \"@qrkit/bc-ur\";\n\nexport function encodeURParts(\n cbor: Uint8Array,\n type: string,\n maxFragmentLength = 200,\n): string[] {\n const ur = UR.fromCbor({ type, payload: cbor });\n const encoder = new UrFountainEncoder(ur, maxFragmentLength);\n return encoder.getAllPartsUr().map((part) => part.toString().toUpperCase());\n}\n","import { encode, CborTag } from \"../cbor.js\";\nimport { encodeURParts } from \"../urEncoding.js\";\n\n// EIP-191 personal_sign = eth-raw-bytes (ERC-4527 data type 3)\nconst DATA_TYPE_ETH_RAW_BYTES = 3;\n\n// CBOR tag for crypto-keypath\nconst TAG_KEYPATH = 304;\n\nfunction randomBytes(n: number): Uint8Array {\n const buf = new Uint8Array(n);\n crypto.getRandomValues(buf);\n return buf;\n}\n\nfunction buildKeypath(\n purpose: number,\n coinType: number,\n sourceFingerprint: number | undefined,\n): CborTag {\n // m/purpose'/coinType'/0'/0/0\n const components = [purpose, true, coinType, true, 0, true, 0, false, 0, false];\n const keypathMap = new Map<number, unknown>([[1, components]]);\n if (sourceFingerprint !== undefined) {\n keypathMap.set(2, sourceFingerprint);\n }\n return new CborTag(TAG_KEYPATH, keypathMap);\n}\n\nfunction buildEthSignRequestCbor(\n message: string,\n address: string,\n sourceFingerprint: number | undefined,\n origin = \"qrkit\",\n): Uint8Array {\n const requestId = randomBytes(16);\n const messageBytes = new TextEncoder().encode(message);\n const keypath = buildKeypath(44, 60, sourceFingerprint);\n\n const addrHex = address.replace(/^0x/i, \"\");\n const addrBytes = new Uint8Array(addrHex.match(/.{2}/g)!.map((b) => parseInt(b, 16)));\n\n const map = new Map<number, unknown>([\n [1, new CborTag(37, requestId)], // request-id: uuid = #6.37(bstr)\n [2, messageBytes], // sign-data\n [3, DATA_TYPE_ETH_RAW_BYTES], // data-type: eth-raw-bytes for EIP-191\n [5, keypath], // derivation-path\n [6, addrBytes], // address\n [7, origin], // origin\n ]);\n\n return encode(map);\n}\n\nexport function buildEthSignRequestURParts(\n message: string,\n address: string,\n sourceFingerprint: number | undefined,\n origin?: string,\n): string[] {\n return encodeURParts(\n buildEthSignRequestCbor(message, address, sourceFingerprint, origin),\n \"eth-sign-request\",\n );\n}\n\nexport function buildEthSignRequestUR(\n message: string,\n address: string,\n sourceFingerprint: number | undefined,\n origin?: string,\n): string {\n return encodeURParts(\n buildEthSignRequestCbor(message, address, sourceFingerprint, origin),\n \"eth-sign-request\",\n )[0];\n}\n","import { decode as cborDecode, type TagDecoder } from \"cborg\";\n\nimport type { ScannedUR } from \"../types.js\";\n\nexport function parseEthSignature(scanned: ScannedUR): string {\n if (scanned.type !== \"eth-signature\") {\n throw new Error(`Expected eth-signature, got: ${scanned.type}`);\n }\n\n const map = cborDecode(scanned.cbor, {\n useMaps: true,\n tags: Object.assign([] as TagDecoder[], { 37: (v: unknown) => v }),\n }) as Map<number, unknown>;\n\n const sigBytes = map.get(2) as Uint8Array | undefined;\n if (!sigBytes || sigBytes.length < 64) {\n throw new Error(\"Invalid or missing signature bytes\");\n }\n\n return \"0x\" + [...sigBytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAsB;AACtB,mBAAsD;AAkBtD,SAAS,IAAI,GAAY,GAAoB;AAC3C,MAAI,aAAa,IAAK,QAAQ,EAA2B,IAAI,CAAC;AAC9D,SAAO;AACT;AAEA,IAAM,cAAc,CAAC,MAAe;AAEpC,SAAS,WAAW,MAAwC;AAC1D,aAAO,aAAAA,QAAW,MAAM;AAAA,IACtB,SAAS;AAAA,IACT,MAAM,OAAO,OAAO,CAAC,GAAmB;AAAA,MACtC,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,IACP,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,iBAAiB,KAAc,KAAyB;AAC/D,QAAM,UAAU,IAAI,KAAK,CAAC;AAC1B,QAAM,YAAY,IAAI,KAAK,CAAC;AAE5B,MAAI,CAAC,WAAW,CAAC,WAAW;AAC1B,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,QAAM,SAAS,IAAI,KAAK,CAAC;AACzB,MAAI,QAAQ;AACV,UAAM,aAAa,IAAI,QAAQ,CAAC;AAChC,QAAI,MAAM,QAAQ,UAAU,GAAG;AAC7B,UAAI,WAAW,UAAU,EAAG,WAAU,WAAW,CAAC;AAClD,UAAI,WAAW,UAAU,EAAG,YAAW,WAAW,CAAC;AAAA,IACrD;AACA,wBAAoB,IAAI,QAAQ,CAAC;AAAA,EACnC;AAEA,QAAM,QAAQ,IAAI,mBAAM,EAAE,WAAW,SAAS,UAAU,CAAC;AACzD,SAAO,EAAE,OAAO,MAAM,QAAQ,SAAS,UAAU,mBAAmB,IAAI;AAC1E;AAEA,SAAS,eAAe,SAA+C;AACrE,QAAM,EAAE,MAAM,KAAK,IAAI;AACvB,QAAM,MAAM,MAAM,IAAI;AAEtB,MAAI,SAAS,kBAAkB,SAAS,kBAAkB;AACxD,UAAM,IAAI,MAAM,wBAAwB,IAAI,EAAE;AAAA,EAChD;AAEA,QAAM,MAAM,WAAW,IAAI;AAE3B,MAAI,SAAS,gBAAgB;AAC3B,WAAO,iBAAiB,KAAK,GAAG;AAAA,EAClC;AAEA,QAAM,WAAW,IAAI,IAAI,CAAC;AAC1B,MAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAAG;AACrD,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,SAAO,SAAS,IAAI,CAAC,UAAU,iBAAiB,OAAO,GAAG,CAAC;AAC7D;AAEO,SAAS,UAAU,OAAyC;AACjE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,eAAe,KAAK;AACnC,WAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AAAA,EACjD;AAEA,QAAM,QAAQ,mBAAM,gBAAgB,MAAM,KAAK,CAAC;AAChD,SAAO;AAAA,IACL;AAAA,MACE;AAAA,MACA,MAAM;AAAA,MACN,SAAS;AAAA,MACT,UAAU;AAAA,MACV,mBAAmB;AAAA,MACnB,KAAK,MAAM,KAAK;AAAA,IAClB;AAAA,EACF;AACF;;;ACpGA,kBAA2B;AAC3B,WAAsB;AAEf,SAAS,mBAAmB,kBAAsC;AACvE,QAAM,eAAoB,WAAM,UAAU,gBAAgB,EAAE,QAAQ,KAAK;AACzE,QAAM,WAAO,wBAAW,aAAa,MAAM,CAAC,CAAC;AAC7C,QAAM,MAAM,CAAC,GAAG,KAAK,MAAM,EAAE,CAAC,EAC3B,IAAI,CAAC,MAAc,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAClD,KAAK,EAAE;AACV,SAAO,kBAAkB,GAAG;AAC9B;AAEA,SAAS,kBAAkB,KAAqB;AAC9C,QAAM,mBAAe,wBAAW,IAAI,YAAY,EAAE,OAAO,GAAG,CAAC;AAC7D,SACE,OACA,CAAC,GAAG,GAAG,EACJ,IAAI,CAAC,GAAG,MAAM;AACb,QAAI,KAAK,OAAO,KAAK,IAAK,QAAO;AACjC,UAAM,SACJ,IAAI,MAAM,IACL,aAAa,KAAK,MAAM,IAAI,CAAC,CAAC,KAAK,IAAK,KACzC,aAAa,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI;AACxC,WAAO,UAAU,IAAI,EAAE,YAAY,IAAI,EAAE,YAAY;AAAA,EACvD,CAAC,EACA,KAAK,EAAE;AAEd;;;ACZA,SAAS,WAAW,YAA0B;AAC5C,SAAO,WAAW,YAAY,CAAC,EAAE,YAAY,CAAC;AAChD;AAEA,SAAS,MAAM,OAA2B;AACxC,SAAO,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACvE;AAEO,SAAS,iBAAiB,QAAkD;AACjF,aAAW,SAAS,QAAQ;AAC1B,UAAM,EAAE,OAAO,SAAS,UAAU,MAAM,kBAAkB,IAAI;AAC9D,UAAM,QACH,YAAY,MAAM,aAAa,MAAQ,YAAY,UAAa,SAAS;AAE5E,QAAI,CAAC,MAAO;AAEZ,UAAM,QAAQ,WAAW,KAAK;AAC9B,QAAI,CAAC,MAAM,UAAW;AAEtB,WAAO;AAAA,MACL,SAAS,mBAAmB,MAAM,SAAS;AAAA,MAC3C,WAAW,MAAM,MAAM,SAAS;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACrCA,IAAM,aAAsB,CAAC,OAAO,KAAK;AAUlC,SAAS,gBACd,WACA,SAAsB,CAAC,GACZ;AACX,QAAM,SAAS,OAAO,UAAU;AAChC,QAAM,SAAS,UAAU,SAAS;AAClC,QAAM,WAAsB,CAAC;AAE7B,MAAI,OAAO,SAAS,KAAK,GAAG;AAC1B,UAAM,UAAU,iBAAiB,MAAM;AACvC,QAAI,SAAS;AACX,eAAS,KAAK,EAAE,OAAO,OAAO,GAAG,QAAQ,CAAsB;AAAA,IACjE;AAAA,EACF;AAIA,SAAO;AACT;;;AC7BA,SAAS,UAAU,OAAe,GAAqB;AACrD,QAAM,OAAO,SAAS;AACtB,MAAI,KAAK,GAAI,QAAO,CAAC,OAAO,CAAC;AAC7B,MAAI,KAAK,IAAM,QAAO,CAAC,OAAO,IAAM,CAAC;AACrC,MAAI,KAAK,MAAQ,QAAO,CAAC,OAAO,IAAO,KAAK,IAAK,KAAM,IAAI,GAAI;AAC/D,SAAO,CAAC,OAAO,IAAO,KAAK,KAAM,KAAO,KAAK,KAAM,KAAO,KAAK,IAAK,KAAM,IAAI,GAAI;AACpF;AAEO,SAAS,WAAW,OAA0B;AACnD,MAAI,OAAO,UAAU,WAAW;AAC9B,WAAO,CAAC,QAAQ,MAAO,GAAI;AAAA,EAC7B;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,UAAU,GAAG,KAAK;AAAA,EAC3B;AACA,MAAI,iBAAiB,YAAY;AAC/B,WAAO,CAAC,GAAG,UAAU,GAAG,MAAM,MAAM,GAAG,GAAG,KAAK;AAAA,EACjD;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,QAAQ,IAAI,YAAY,EAAE,OAAO,KAAK;AAC5C,WAAO,CAAC,GAAG,UAAU,GAAG,MAAM,MAAM,GAAG,GAAG,KAAK;AAAA,EACjD;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,CAAC,GAAG,UAAU,GAAG,MAAM,MAAM,GAAG,GAAG,MAAM,QAAQ,UAAU,CAAC;AAAA,EACrE;AACA,MAAI,iBAAiB,SAAS;AAC5B,WAAO,CAAC,GAAG,UAAU,GAAG,MAAM,GAAG,GAAG,GAAG,WAAW,MAAM,KAAK,CAAC;AAAA,EAChE;AACA,MAAI,iBAAiB,KAAK;AACxB,UAAM,UAAU,CAAC,GAAG,MAAM,QAAQ,CAAC;AACnC,WAAO;AAAA,MACL,GAAG,UAAU,GAAG,QAAQ,MAAM;AAAA,MAC9B,GAAG,QAAQ,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC;AAAA,IACrE;AAAA,EACF;AACA,QAAM,IAAI,MAAM,2BAA2B,OAAO,KAAK,EAAE;AAC3D;AAEO,IAAM,UAAN,MAAc;AAAA,EACnB;AAAA,EACA;AAAA,EACA,YAAY,KAAa,OAAgB;AACvC,SAAK,MAAM;AACX,SAAK,QAAQ;AAAA,EACf;AACF;AAEO,SAAS,OAAO,OAA4B;AACjD,SAAO,IAAI,WAAW,WAAW,KAAK,CAAC;AACzC;;;ACpDA,mBAAsC;AAE/B,SAAS,cACd,MACA,MACA,oBAAoB,KACV;AACV,QAAM,KAAK,gBAAG,SAAS,EAAE,MAAM,SAAS,KAAK,CAAC;AAC9C,QAAM,UAAU,IAAI,+BAAkB,IAAI,iBAAiB;AAC3D,SAAO,QAAQ,cAAc,EAAE,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,YAAY,CAAC;AAC5E;;;ACNA,IAAM,0BAA0B;AAGhC,IAAM,cAAc;AAEpB,SAAS,YAAY,GAAuB;AAC1C,QAAM,MAAM,IAAI,WAAW,CAAC;AAC5B,SAAO,gBAAgB,GAAG;AAC1B,SAAO;AACT;AAEA,SAAS,aACP,SACA,UACA,mBACS;AAET,QAAM,aAAa,CAAC,SAAS,MAAM,UAAU,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK;AAC9E,QAAM,aAAa,oBAAI,IAAqB,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;AAC7D,MAAI,sBAAsB,QAAW;AACnC,eAAW,IAAI,GAAG,iBAAiB;AAAA,EACrC;AACA,SAAO,IAAI,QAAQ,aAAa,UAAU;AAC5C;AAEA,SAAS,wBACP,SACA,SACA,mBACA,SAAS,SACG;AACZ,QAAM,YAAY,YAAY,EAAE;AAChC,QAAM,eAAe,IAAI,YAAY,EAAE,OAAO,OAAO;AACrD,QAAM,UAAU,aAAa,IAAI,IAAI,iBAAiB;AAEtD,QAAM,UAAU,QAAQ,QAAQ,QAAQ,EAAE;AAC1C,QAAM,YAAY,IAAI,WAAW,QAAQ,MAAM,OAAO,EAAG,IAAI,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC;AAEpF,QAAM,MAAM,oBAAI,IAAqB;AAAA,IACnC,CAAC,GAAG,IAAI,QAAQ,IAAI,SAAS,CAAC;AAAA;AAAA,IAC9B,CAAC,GAAG,YAAY;AAAA;AAAA,IAChB,CAAC,GAAG,uBAAuB;AAAA;AAAA,IAC3B,CAAC,GAAG,OAAO;AAAA;AAAA,IACX,CAAC,GAAG,SAAS;AAAA;AAAA,IACb,CAAC,GAAG,MAAM;AAAA;AAAA,EACZ,CAAC;AAED,SAAO,OAAO,GAAG;AACnB;AAEO,SAAS,2BACd,SACA,SACA,mBACA,QACU;AACV,SAAO;AAAA,IACL,wBAAwB,SAAS,SAAS,mBAAmB,MAAM;AAAA,IACnE;AAAA,EACF;AACF;AAEO,SAAS,sBACd,SACA,SACA,mBACA,QACQ;AACR,SAAO;AAAA,IACL,wBAAwB,SAAS,SAAS,mBAAmB,MAAM;AAAA,IACnE;AAAA,EACF,EAAE,CAAC;AACL;;;AC5EA,IAAAC,gBAAsD;AAI/C,SAAS,kBAAkB,SAA4B;AAC5D,MAAI,QAAQ,SAAS,iBAAiB;AACpC,UAAM,IAAI,MAAM,gCAAgC,QAAQ,IAAI,EAAE;AAAA,EAChE;AAEA,QAAM,UAAM,cAAAC,QAAW,QAAQ,MAAM;AAAA,IACnC,SAAS;AAAA,IACT,MAAM,OAAO,OAAO,CAAC,GAAmB,EAAE,IAAI,CAAC,MAAe,EAAE,CAAC;AAAA,EACnE,CAAC;AAED,QAAM,WAAW,IAAI,IAAI,CAAC;AAC1B,MAAI,CAAC,YAAY,SAAS,SAAS,IAAI;AACrC,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AAEA,SAAO,OAAO,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACjF;","names":["cborDecode","import_cborg","cborDecode"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/parseXpub.ts","../src/eth/address.ts","../src/eth/deriveAccount.ts","../src/parseConnection.ts","../src/cbor.ts","../src/urEncoding.ts","../src/eth/signRequest.ts","../src/eth/signature.ts"],"sourcesContent":["// Types\nexport type { ScannedUR, Chain, QRKitConfig, Account, EvmAccount } from \"./types.js\";\nexport type { ParsedXpub, XpubType } from \"./parseXpub.js\";\n\n// Connection\nexport { parseConnection } from \"./parseConnection.js\";\n\n// EVM signing\nexport {\n buildEthSignRequestUR,\n buildEthSignRequestURParts,\n EthDataType,\n} from \"./eth/signRequest.js\";\nexport type { EthDataTypeValue, EthSignRequestParams } from \"./eth/signRequest.js\";\nexport { parseEthSignature } from \"./eth/signature.js\";\n","import { HDKey } from \"@scure/bip32\";\nimport { decode as cborDecode, type TagDecoder } from \"cborg\";\n\nimport type { ScannedUR } from \"./types.js\";\n\nexport type XpubType = \"xpub\";\n\nexport interface ParsedXpub {\n hdKey: HDKey;\n type: XpubType;\n /** BIP-44 purpose index: 44 for EVM */\n purpose: number | undefined;\n /** BIP-44 coin type: 60 = ETH */\n coinType: number | undefined;\n /** source-fingerprint from the origin keypath — required by Shell for signing */\n sourceFingerprint: number | undefined;\n /** device or key name from crypto-hdkey key 9, set by some wallets (e.g. Keystone) */\n name?: string;\n raw: string;\n}\n\nfunction get(m: unknown, k: number): unknown {\n if (m instanceof Map) return (m as Map<number, unknown>).get(k);\n return undefined;\n}\n\nconst passthrough = (v: unknown) => v;\n\nfunction decodeCbor(cbor: Uint8Array): Map<number, unknown> {\n return cborDecode(cbor, {\n useMaps: true,\n tags: Object.assign([] as TagDecoder[], {\n 303: passthrough, // crypto-hdkey\n 304: passthrough, // crypto-keypath\n 305: passthrough, // crypto-coin-info\n }),\n }) as Map<number, unknown>;\n}\n\nfunction parseCryptoHdKey(map: unknown, raw: string): ParsedXpub {\n const keyData = get(map, 3) as Uint8Array | undefined;\n const chainCode = get(map, 4) as Uint8Array | undefined;\n\n if (!keyData || !chainCode) {\n throw new Error(\"crypto-hdkey missing key-data or chain-code\");\n }\n\n let purpose: number | undefined;\n let coinType: number | undefined;\n let sourceFingerprint: number | undefined;\n const origin = get(map, 6);\n if (origin) {\n const components = get(origin, 1);\n if (Array.isArray(components)) {\n if (components.length >= 1) purpose = components[0] as number;\n if (components.length >= 3) coinType = components[2] as number;\n }\n sourceFingerprint = get(origin, 2) as number | undefined;\n }\n\n const name = get(map, 9) as string | undefined;\n\n const hdKey = new HDKey({ publicKey: keyData, chainCode });\n return { hdKey, type: \"xpub\", purpose, coinType, sourceFingerprint, name, raw };\n}\n\nfunction parseScannedUR(scanned: ScannedUR): ParsedXpub | ParsedXpub[] {\n const { type, cbor } = scanned;\n const raw = `ur:${type}`;\n\n if (type !== \"crypto-hdkey\" && type !== \"crypto-account\") {\n throw new Error(`Unsupported UR type: ${type}`);\n }\n\n const map = decodeCbor(cbor);\n\n if (type === \"crypto-hdkey\") {\n return parseCryptoHdKey(map, raw);\n }\n\n const accounts = map.get(2) as unknown[] | undefined;\n if (!Array.isArray(accounts) || accounts.length === 0) {\n throw new Error(\"crypto-account contains no keys\");\n }\n return accounts.map((entry) => parseCryptoHdKey(entry, raw));\n}\n\nexport function parseXpub(input: ScannedUR | string): ParsedXpub[] {\n if (typeof input !== \"string\") {\n const result = parseScannedUR(input);\n return Array.isArray(result) ? result : [result];\n }\n // Raw base58 xpub string — @scure/bip32 handles decoding directly\n const hdKey = HDKey.fromExtendedKey(input.trim());\n return [\n {\n hdKey,\n type: \"xpub\",\n purpose: undefined,\n coinType: undefined,\n sourceFingerprint: undefined,\n raw: input.trim(),\n },\n ];\n}\n","import { keccak_256 } from \"@noble/hashes/sha3.js\";\nimport * as secp from \"@noble/secp256k1\";\n\nexport function pubKeyToEthAddress(compressedPubKey: Uint8Array): string {\n const uncompressed = secp.Point.fromBytes(compressedPubKey).toBytes(false);\n const hash = keccak_256(uncompressed.slice(1));\n const hex = [...hash.slice(12)]\n .map((b: number) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n return toChecksumAddress(hex);\n}\n\nfunction toChecksumAddress(hex: string): string {\n const checksumHash = keccak_256(new TextEncoder().encode(hex));\n return (\n \"0x\" +\n [...hex]\n .map((c, i) => {\n if (c >= \"0\" && c <= \"9\") return c;\n const nibble =\n i % 2 === 0\n ? (checksumHash[Math.floor(i / 2)] >> 4) & 0xf\n : checksumHash[Math.floor(i / 2)] & 0xf;\n return nibble >= 8 ? c.toUpperCase() : c.toLowerCase();\n })\n .join(\"\")\n );\n}\n","import type { HDKey } from \"@scure/bip32\";\n\nimport type { ParsedXpub } from \"../parseXpub.js\";\nimport { pubKeyToEthAddress } from \"./address.js\";\n\nexport interface DerivedAccount {\n address: string;\n publicKey: string;\n /** source-fingerprint from the scanned xpub — required by Shell for signing */\n sourceFingerprint: number | undefined;\n /** device or key name as reported by the hardware wallet, if available */\n device: string | undefined;\n}\n\n// Derive the first external address (index 0) from an account-level xpub.\n// Account-level xpub is at depth 3 (m/purpose'/coin'/account').\n// External chain is child 0, then address index 0.\nfunction firstChild(accountKey: HDKey): HDKey {\n return accountKey.deriveChild(0).deriveChild(0);\n}\n\nfunction toHex(bytes: Uint8Array): string {\n return [...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function deriveEvmAccount(parsed: ParsedXpub[]): DerivedAccount | undefined {\n for (const entry of parsed) {\n const { hdKey, purpose, coinType, type, sourceFingerprint, name } = entry;\n const isEvm =\n (purpose === 44 && coinType === 60) || (purpose === undefined && type === \"xpub\");\n\n if (!isEvm) continue;\n\n const child = firstChild(hdKey);\n if (!child.publicKey) continue;\n\n return {\n address: pubKeyToEthAddress(child.publicKey),\n publicKey: toHex(child.publicKey),\n sourceFingerprint,\n device: name,\n };\n }\n return undefined;\n}\n","import { parseXpub } from \"./parseXpub.js\";\nimport { deriveEvmAccount } from \"./eth/deriveAccount.js\";\nimport type { Account, Chain, EvmAccount, QRKitConfig, ScannedUR } from \"./types.js\";\n\nconst ALL_CHAINS: Chain[] = [\"evm\", \"btc\"];\n\n/**\n * Parse a connection QR (crypto-hdkey or crypto-account) and return\n * only the accounts for the chains configured in QRKitConfig.\n *\n * A dApp configured with `chains: [\"evm\"]` will never see BTC accounts,\n * and vice versa. Both chains can be enabled with `chains: [\"evm\", \"btc\"]`.\n * If `chains` is omitted, all supported chains are tried.\n */\nexport function parseConnection(\n scannedUR: ScannedUR,\n config: QRKitConfig = {},\n): Account[] {\n const chains = config.chains ?? ALL_CHAINS;\n const parsed = parseXpub(scannedUR);\n const accounts: Account[] = [];\n\n if (chains.includes(\"evm\")) {\n const account = deriveEvmAccount(parsed);\n if (account) {\n accounts.push({ chain: \"evm\", ...account } satisfies EvmAccount);\n }\n }\n\n // \"btc\" — will be implemented in src/btc/ when Bitcoin support is added.\n\n return accounts;\n}\n","// Minimal CBOR encoder.\n// Supports only the types needed for eth-sign-request: uint, bytes, text, array, map (integer keys), bool, tag.\n\nfunction majorType(major: number, n: number): number[] {\n const base = major << 5;\n if (n <= 23) return [base | n];\n if (n <= 0xff) return [base | 0x18, n];\n if (n <= 0xffff) return [base | 0x19, (n >> 8) & 0xff, n & 0xff];\n return [base | 0x1a, (n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];\n}\n\nexport function encodeItem(value: unknown): number[] {\n if (typeof value === \"boolean\") {\n return [value ? 0xf5 : 0xf4];\n }\n if (typeof value === \"number\") {\n return majorType(0, value);\n }\n if (value instanceof Uint8Array) {\n return [...majorType(2, value.length), ...value];\n }\n if (typeof value === \"string\") {\n const bytes = new TextEncoder().encode(value);\n return [...majorType(3, bytes.length), ...bytes];\n }\n if (Array.isArray(value)) {\n return [...majorType(4, value.length), ...value.flatMap(encodeItem)];\n }\n if (value instanceof CborTag) {\n return [...majorType(6, value.tag), ...encodeItem(value.value)];\n }\n if (value instanceof Map) {\n const entries = [...value.entries()];\n return [\n ...majorType(5, entries.length),\n ...entries.flatMap(([k, v]) => [...encodeItem(k), ...encodeItem(v)]),\n ];\n }\n throw new Error(`Unsupported CBOR value: ${typeof value}`);\n}\n\nexport class CborTag {\n tag: number;\n value: unknown;\n constructor(tag: number, value: unknown) {\n this.tag = tag;\n this.value = value;\n }\n}\n\nexport function encode(value: unknown): Uint8Array {\n return new Uint8Array(encodeItem(value));\n}\n","import { UR, UrFountainEncoder } from \"@qrkit/bc-ur\";\n\nexport function encodeURParts(\n cbor: Uint8Array,\n type: string,\n maxFragmentLength = 200,\n): string[] {\n const ur = UR.fromCbor({ type, payload: cbor });\n const encoder = new UrFountainEncoder(ur, maxFragmentLength);\n return encoder.getAllPartsUr().map((part) => part.toString().toUpperCase());\n}\n","import { encode, CborTag } from \"../cbor.js\";\nimport { encodeURParts } from \"../urEncoding.js\";\n\n// ERC-4527 eth-sign-request data types\nexport const EthDataType = {\n /** Legacy transaction (RLP-encoded). Wallet applies EIP-155: v = 35 + 2*chainId + recId */\n LegacyTransaction: 1,\n /** EIP-712 typed data bytes. Wallet hashes internally. */\n TypedData: 2,\n /** Personal message (EIP-191). Wallet prepends \"\\x19Ethereum Signed Message:\\n{len}\". */\n PersonalMessage: 3,\n /** EIP-1559 transaction (RLP-encoded). Wallet uses v = recId. */\n TypedTransaction: 4,\n} as const;\n\nexport type EthDataTypeValue = (typeof EthDataType)[keyof typeof EthDataType];\n\n// CBOR tag for crypto-keypath\nconst TAG_KEYPATH = 304;\n\nfunction randomBytes(n: number): Uint8Array {\n const buf = new Uint8Array(n);\n crypto.getRandomValues(buf);\n return buf;\n}\n\nfunction buildKeypath(\n purpose: number,\n coinType: number,\n sourceFingerprint: number | undefined,\n): CborTag {\n // m/purpose'/coinType'/0'/0/0\n const components = [purpose, true, coinType, true, 0, true, 0, false, 0, false];\n const keypathMap = new Map<number, unknown>([[1, components]]);\n if (sourceFingerprint !== undefined) {\n keypathMap.set(2, sourceFingerprint);\n }\n return new CborTag(TAG_KEYPATH, keypathMap);\n}\n\nexport interface EthSignRequestParams {\n /** Raw sign data. For PersonalMessage, a string is UTF-8 encoded automatically. */\n signData: Uint8Array | string;\n /** ERC-4527 data type. Defaults to PersonalMessage (3). Use EthDataType constants. */\n dataType?: number;\n address: string;\n sourceFingerprint: number | undefined;\n /** Chain ID — required by the wallet for v-value encoding on LegacyTransaction (type 1). */\n chainId?: number;\n origin?: string;\n}\n\nfunction buildEthSignRequestCbor(params: EthSignRequestParams): Uint8Array {\n const {\n signData,\n dataType = EthDataType.PersonalMessage,\n address,\n sourceFingerprint,\n chainId,\n origin = \"qrkit\",\n } = params;\n\n const requestId = randomBytes(16);\n const signBytes =\n typeof signData === \"string\" ? new TextEncoder().encode(signData) : signData;\n const keypath = buildKeypath(44, 60, sourceFingerprint);\n\n const addrHex = address.replace(/^0x/i, \"\");\n const addrBytes = new Uint8Array(addrHex.match(/.{2}/g)!.map((b) => parseInt(b, 16)));\n\n // Keys must be in strictly ascending order for zcbor (Shell firmware decoder).\n const map = new Map<number, unknown>([\n [1, new CborTag(37, requestId)], // request-id: uuid = #6.37(bstr)\n [2, signBytes], // sign-data\n [3, dataType], // data-type\n ]);\n\n if (chainId !== undefined) {\n map.set(4, chainId); // chain-id — must come before key 5\n }\n\n map.set(5, keypath); // derivation-path\n map.set(6, addrBytes); // address\n map.set(7, origin); // origin\n\n return encode(map);\n}\n\nexport function buildEthSignRequestURParts(params: EthSignRequestParams): string[] {\n return encodeURParts(buildEthSignRequestCbor(params), \"eth-sign-request\");\n}\n\nexport function buildEthSignRequestUR(params: EthSignRequestParams): string {\n return encodeURParts(buildEthSignRequestCbor(params), \"eth-sign-request\")[0];\n}\n","import { decode as cborDecode, type TagDecoder } from \"cborg\";\n\nimport type { ScannedUR } from \"../types.js\";\n\nexport function parseEthSignature(scanned: ScannedUR): string {\n if (scanned.type !== \"eth-signature\") {\n throw new Error(`Expected eth-signature, got: ${scanned.type}`);\n }\n\n const map = cborDecode(scanned.cbor, {\n useMaps: true,\n tags: Object.assign([] as TagDecoder[], { 37: (v: unknown) => v }),\n }) as Map<number, unknown>;\n\n const sigBytes = map.get(2) as Uint8Array | undefined;\n if (!sigBytes || sigBytes.length < 64) {\n throw new Error(\"Invalid or missing signature bytes\");\n }\n\n return \"0x\" + [...sigBytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n"],"mappings":"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","names":["cborDecode","import_cborg","cborDecode"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -16,6 +16,8 @@ interface EvmAccount {
|
|
|
16
16
|
publicKey: string;
|
|
17
17
|
/** source-fingerprint from the scanned xpub — required by Shell for signing */
|
|
18
18
|
sourceFingerprint: number | undefined;
|
|
19
|
+
/** device or key name as reported by the hardware wallet, if available */
|
|
20
|
+
device: string | undefined;
|
|
19
21
|
}
|
|
20
22
|
type Account = EvmAccount;
|
|
21
23
|
|
|
@@ -29,6 +31,8 @@ interface ParsedXpub {
|
|
|
29
31
|
coinType: number | undefined;
|
|
30
32
|
/** source-fingerprint from the origin keypath — required by Shell for signing */
|
|
31
33
|
sourceFingerprint: number | undefined;
|
|
34
|
+
/** device or key name from crypto-hdkey key 9, set by some wallets (e.g. Keystone) */
|
|
35
|
+
name?: string;
|
|
32
36
|
raw: string;
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -42,9 +46,31 @@ interface ParsedXpub {
|
|
|
42
46
|
*/
|
|
43
47
|
declare function parseConnection(scannedUR: ScannedUR, config?: QRKitConfig): Account[];
|
|
44
48
|
|
|
45
|
-
declare
|
|
46
|
-
|
|
49
|
+
declare const EthDataType: {
|
|
50
|
+
/** Legacy transaction (RLP-encoded). Wallet applies EIP-155: v = 35 + 2*chainId + recId */
|
|
51
|
+
readonly LegacyTransaction: 1;
|
|
52
|
+
/** EIP-712 typed data bytes. Wallet hashes internally. */
|
|
53
|
+
readonly TypedData: 2;
|
|
54
|
+
/** Personal message (EIP-191). Wallet prepends "\x19Ethereum Signed Message:\n{len}". */
|
|
55
|
+
readonly PersonalMessage: 3;
|
|
56
|
+
/** EIP-1559 transaction (RLP-encoded). Wallet uses v = recId. */
|
|
57
|
+
readonly TypedTransaction: 4;
|
|
58
|
+
};
|
|
59
|
+
type EthDataTypeValue = (typeof EthDataType)[keyof typeof EthDataType];
|
|
60
|
+
interface EthSignRequestParams {
|
|
61
|
+
/** Raw sign data. For PersonalMessage, a string is UTF-8 encoded automatically. */
|
|
62
|
+
signData: Uint8Array | string;
|
|
63
|
+
/** ERC-4527 data type. Defaults to PersonalMessage (3). Use EthDataType constants. */
|
|
64
|
+
dataType?: number;
|
|
65
|
+
address: string;
|
|
66
|
+
sourceFingerprint: number | undefined;
|
|
67
|
+
/** Chain ID — required by the wallet for v-value encoding on LegacyTransaction (type 1). */
|
|
68
|
+
chainId?: number;
|
|
69
|
+
origin?: string;
|
|
70
|
+
}
|
|
71
|
+
declare function buildEthSignRequestURParts(params: EthSignRequestParams): string[];
|
|
72
|
+
declare function buildEthSignRequestUR(params: EthSignRequestParams): string;
|
|
47
73
|
|
|
48
74
|
declare function parseEthSignature(scanned: ScannedUR): string;
|
|
49
75
|
|
|
50
|
-
export { type Account, type Chain, type EvmAccount, type ParsedXpub, type QRKitConfig, type ScannedUR, type XpubType, buildEthSignRequestUR, buildEthSignRequestURParts, parseConnection, parseEthSignature };
|
|
76
|
+
export { type Account, type Chain, EthDataType, type EthDataTypeValue, type EthSignRequestParams, type EvmAccount, type ParsedXpub, type QRKitConfig, type ScannedUR, type XpubType, buildEthSignRequestUR, buildEthSignRequestURParts, parseConnection, parseEthSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ interface EvmAccount {
|
|
|
16
16
|
publicKey: string;
|
|
17
17
|
/** source-fingerprint from the scanned xpub — required by Shell for signing */
|
|
18
18
|
sourceFingerprint: number | undefined;
|
|
19
|
+
/** device or key name as reported by the hardware wallet, if available */
|
|
20
|
+
device: string | undefined;
|
|
19
21
|
}
|
|
20
22
|
type Account = EvmAccount;
|
|
21
23
|
|
|
@@ -29,6 +31,8 @@ interface ParsedXpub {
|
|
|
29
31
|
coinType: number | undefined;
|
|
30
32
|
/** source-fingerprint from the origin keypath — required by Shell for signing */
|
|
31
33
|
sourceFingerprint: number | undefined;
|
|
34
|
+
/** device or key name from crypto-hdkey key 9, set by some wallets (e.g. Keystone) */
|
|
35
|
+
name?: string;
|
|
32
36
|
raw: string;
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -42,9 +46,31 @@ interface ParsedXpub {
|
|
|
42
46
|
*/
|
|
43
47
|
declare function parseConnection(scannedUR: ScannedUR, config?: QRKitConfig): Account[];
|
|
44
48
|
|
|
45
|
-
declare
|
|
46
|
-
|
|
49
|
+
declare const EthDataType: {
|
|
50
|
+
/** Legacy transaction (RLP-encoded). Wallet applies EIP-155: v = 35 + 2*chainId + recId */
|
|
51
|
+
readonly LegacyTransaction: 1;
|
|
52
|
+
/** EIP-712 typed data bytes. Wallet hashes internally. */
|
|
53
|
+
readonly TypedData: 2;
|
|
54
|
+
/** Personal message (EIP-191). Wallet prepends "\x19Ethereum Signed Message:\n{len}". */
|
|
55
|
+
readonly PersonalMessage: 3;
|
|
56
|
+
/** EIP-1559 transaction (RLP-encoded). Wallet uses v = recId. */
|
|
57
|
+
readonly TypedTransaction: 4;
|
|
58
|
+
};
|
|
59
|
+
type EthDataTypeValue = (typeof EthDataType)[keyof typeof EthDataType];
|
|
60
|
+
interface EthSignRequestParams {
|
|
61
|
+
/** Raw sign data. For PersonalMessage, a string is UTF-8 encoded automatically. */
|
|
62
|
+
signData: Uint8Array | string;
|
|
63
|
+
/** ERC-4527 data type. Defaults to PersonalMessage (3). Use EthDataType constants. */
|
|
64
|
+
dataType?: number;
|
|
65
|
+
address: string;
|
|
66
|
+
sourceFingerprint: number | undefined;
|
|
67
|
+
/** Chain ID — required by the wallet for v-value encoding on LegacyTransaction (type 1). */
|
|
68
|
+
chainId?: number;
|
|
69
|
+
origin?: string;
|
|
70
|
+
}
|
|
71
|
+
declare function buildEthSignRequestURParts(params: EthSignRequestParams): string[];
|
|
72
|
+
declare function buildEthSignRequestUR(params: EthSignRequestParams): string;
|
|
47
73
|
|
|
48
74
|
declare function parseEthSignature(scanned: ScannedUR): string;
|
|
49
75
|
|
|
50
|
-
export { type Account, type Chain, type EvmAccount, type ParsedXpub, type QRKitConfig, type ScannedUR, type XpubType, buildEthSignRequestUR, buildEthSignRequestURParts, parseConnection, parseEthSignature };
|
|
76
|
+
export { type Account, type Chain, EthDataType, type EthDataTypeValue, type EthSignRequestParams, type EvmAccount, type ParsedXpub, type QRKitConfig, type ScannedUR, type XpubType, buildEthSignRequestUR, buildEthSignRequestURParts, parseConnection, parseEthSignature };
|
package/dist/index.js
CHANGED
|
@@ -37,8 +37,9 @@ function parseCryptoHdKey(map, raw) {
|
|
|
37
37
|
}
|
|
38
38
|
sourceFingerprint = get(origin, 2);
|
|
39
39
|
}
|
|
40
|
+
const name = get(map, 9);
|
|
40
41
|
const hdKey = new HDKey({ publicKey: keyData, chainCode });
|
|
41
|
-
return { hdKey, type: "xpub", purpose, coinType, sourceFingerprint, raw };
|
|
42
|
+
return { hdKey, type: "xpub", purpose, coinType, sourceFingerprint, name, raw };
|
|
42
43
|
}
|
|
43
44
|
function parseScannedUR(scanned) {
|
|
44
45
|
const { type, cbor } = scanned;
|
|
@@ -101,7 +102,7 @@ function toHex(bytes) {
|
|
|
101
102
|
}
|
|
102
103
|
function deriveEvmAccount(parsed) {
|
|
103
104
|
for (const entry of parsed) {
|
|
104
|
-
const { hdKey, purpose, coinType, type, sourceFingerprint } = entry;
|
|
105
|
+
const { hdKey, purpose, coinType, type, sourceFingerprint, name } = entry;
|
|
105
106
|
const isEvm = purpose === 44 && coinType === 60 || purpose === void 0 && type === "xpub";
|
|
106
107
|
if (!isEvm) continue;
|
|
107
108
|
const child = firstChild(hdKey);
|
|
@@ -109,7 +110,8 @@ function deriveEvmAccount(parsed) {
|
|
|
109
110
|
return {
|
|
110
111
|
address: pubKeyToEthAddress(child.publicKey),
|
|
111
112
|
publicKey: toHex(child.publicKey),
|
|
112
|
-
sourceFingerprint
|
|
113
|
+
sourceFingerprint,
|
|
114
|
+
device: name
|
|
113
115
|
};
|
|
114
116
|
}
|
|
115
117
|
return void 0;
|
|
@@ -188,7 +190,16 @@ function encodeURParts(cbor, type, maxFragmentLength = 200) {
|
|
|
188
190
|
}
|
|
189
191
|
|
|
190
192
|
// src/eth/signRequest.ts
|
|
191
|
-
var
|
|
193
|
+
var EthDataType = {
|
|
194
|
+
/** Legacy transaction (RLP-encoded). Wallet applies EIP-155: v = 35 + 2*chainId + recId */
|
|
195
|
+
LegacyTransaction: 1,
|
|
196
|
+
/** EIP-712 typed data bytes. Wallet hashes internally. */
|
|
197
|
+
TypedData: 2,
|
|
198
|
+
/** Personal message (EIP-191). Wallet prepends "\x19Ethereum Signed Message:\n{len}". */
|
|
199
|
+
PersonalMessage: 3,
|
|
200
|
+
/** EIP-1559 transaction (RLP-encoded). Wallet uses v = recId. */
|
|
201
|
+
TypedTransaction: 4
|
|
202
|
+
};
|
|
192
203
|
var TAG_KEYPATH = 304;
|
|
193
204
|
function randomBytes(n) {
|
|
194
205
|
const buf = new Uint8Array(n);
|
|
@@ -203,39 +214,41 @@ function buildKeypath(purpose, coinType, sourceFingerprint) {
|
|
|
203
214
|
}
|
|
204
215
|
return new CborTag(TAG_KEYPATH, keypathMap);
|
|
205
216
|
}
|
|
206
|
-
function buildEthSignRequestCbor(
|
|
217
|
+
function buildEthSignRequestCbor(params) {
|
|
218
|
+
const {
|
|
219
|
+
signData,
|
|
220
|
+
dataType = EthDataType.PersonalMessage,
|
|
221
|
+
address,
|
|
222
|
+
sourceFingerprint,
|
|
223
|
+
chainId,
|
|
224
|
+
origin = "qrkit"
|
|
225
|
+
} = params;
|
|
207
226
|
const requestId = randomBytes(16);
|
|
208
|
-
const
|
|
227
|
+
const signBytes = typeof signData === "string" ? new TextEncoder().encode(signData) : signData;
|
|
209
228
|
const keypath = buildKeypath(44, 60, sourceFingerprint);
|
|
210
229
|
const addrHex = address.replace(/^0x/i, "");
|
|
211
230
|
const addrBytes = new Uint8Array(addrHex.match(/.{2}/g).map((b) => parseInt(b, 16)));
|
|
212
231
|
const map = /* @__PURE__ */ new Map([
|
|
213
232
|
[1, new CborTag(37, requestId)],
|
|
214
233
|
// request-id: uuid = #6.37(bstr)
|
|
215
|
-
[2,
|
|
234
|
+
[2, signBytes],
|
|
216
235
|
// sign-data
|
|
217
|
-
[3,
|
|
218
|
-
// data-type
|
|
219
|
-
[5, keypath],
|
|
220
|
-
// derivation-path
|
|
221
|
-
[6, addrBytes],
|
|
222
|
-
// address
|
|
223
|
-
[7, origin]
|
|
224
|
-
// origin
|
|
236
|
+
[3, dataType]
|
|
237
|
+
// data-type
|
|
225
238
|
]);
|
|
239
|
+
if (chainId !== void 0) {
|
|
240
|
+
map.set(4, chainId);
|
|
241
|
+
}
|
|
242
|
+
map.set(5, keypath);
|
|
243
|
+
map.set(6, addrBytes);
|
|
244
|
+
map.set(7, origin);
|
|
226
245
|
return encode(map);
|
|
227
246
|
}
|
|
228
|
-
function buildEthSignRequestURParts(
|
|
229
|
-
return encodeURParts(
|
|
230
|
-
buildEthSignRequestCbor(message, address, sourceFingerprint, origin),
|
|
231
|
-
"eth-sign-request"
|
|
232
|
-
);
|
|
247
|
+
function buildEthSignRequestURParts(params) {
|
|
248
|
+
return encodeURParts(buildEthSignRequestCbor(params), "eth-sign-request");
|
|
233
249
|
}
|
|
234
|
-
function buildEthSignRequestUR(
|
|
235
|
-
return encodeURParts(
|
|
236
|
-
buildEthSignRequestCbor(message, address, sourceFingerprint, origin),
|
|
237
|
-
"eth-sign-request"
|
|
238
|
-
)[0];
|
|
250
|
+
function buildEthSignRequestUR(params) {
|
|
251
|
+
return encodeURParts(buildEthSignRequestCbor(params), "eth-sign-request")[0];
|
|
239
252
|
}
|
|
240
253
|
|
|
241
254
|
// src/eth/signature.ts
|
|
@@ -255,6 +268,7 @@ function parseEthSignature(scanned) {
|
|
|
255
268
|
return "0x" + [...sigBytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
256
269
|
}
|
|
257
270
|
export {
|
|
271
|
+
EthDataType,
|
|
258
272
|
buildEthSignRequestUR,
|
|
259
273
|
buildEthSignRequestURParts,
|
|
260
274
|
parseConnection,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/parseXpub.ts","../src/eth/address.ts","../src/eth/deriveAccount.ts","../src/parseConnection.ts","../src/cbor.ts","../src/urEncoding.ts","../src/eth/signRequest.ts","../src/eth/signature.ts"],"sourcesContent":["import { HDKey } from \"@scure/bip32\";\nimport { decode as cborDecode, type TagDecoder } from \"cborg\";\n\nimport type { ScannedUR } from \"./types.js\";\n\nexport type XpubType = \"xpub\";\n\nexport interface ParsedXpub {\n hdKey: HDKey;\n type: XpubType;\n /** BIP-44 purpose index: 44 for EVM */\n purpose: number | undefined;\n /** BIP-44 coin type: 60 = ETH */\n coinType: number | undefined;\n /** source-fingerprint from the origin keypath — required by Shell for signing */\n sourceFingerprint: number | undefined;\n raw: string;\n}\n\nfunction get(m: unknown, k: number): unknown {\n if (m instanceof Map) return (m as Map<number, unknown>).get(k);\n return undefined;\n}\n\nconst passthrough = (v: unknown) => v;\n\nfunction decodeCbor(cbor: Uint8Array): Map<number, unknown> {\n return cborDecode(cbor, {\n useMaps: true,\n tags: Object.assign([] as TagDecoder[], {\n 303: passthrough, // crypto-hdkey\n 304: passthrough, // crypto-keypath\n 305: passthrough, // crypto-coin-info\n }),\n }) as Map<number, unknown>;\n}\n\nfunction parseCryptoHdKey(map: unknown, raw: string): ParsedXpub {\n const keyData = get(map, 3) as Uint8Array | undefined;\n const chainCode = get(map, 4) as Uint8Array | undefined;\n\n if (!keyData || !chainCode) {\n throw new Error(\"crypto-hdkey missing key-data or chain-code\");\n }\n\n let purpose: number | undefined;\n let coinType: number | undefined;\n let sourceFingerprint: number | undefined;\n const origin = get(map, 6);\n if (origin) {\n const components = get(origin, 1);\n if (Array.isArray(components)) {\n if (components.length >= 1) purpose = components[0] as number;\n if (components.length >= 3) coinType = components[2] as number;\n }\n sourceFingerprint = get(origin, 2) as number | undefined;\n }\n\n const hdKey = new HDKey({ publicKey: keyData, chainCode });\n return { hdKey, type: \"xpub\", purpose, coinType, sourceFingerprint, raw };\n}\n\nfunction parseScannedUR(scanned: ScannedUR): ParsedXpub | ParsedXpub[] {\n const { type, cbor } = scanned;\n const raw = `ur:${type}`;\n\n if (type !== \"crypto-hdkey\" && type !== \"crypto-account\") {\n throw new Error(`Unsupported UR type: ${type}`);\n }\n\n const map = decodeCbor(cbor);\n\n if (type === \"crypto-hdkey\") {\n return parseCryptoHdKey(map, raw);\n }\n\n const accounts = map.get(2) as unknown[] | undefined;\n if (!Array.isArray(accounts) || accounts.length === 0) {\n throw new Error(\"crypto-account contains no keys\");\n }\n return accounts.map((entry) => parseCryptoHdKey(entry, raw));\n}\n\nexport function parseXpub(input: ScannedUR | string): ParsedXpub[] {\n if (typeof input !== \"string\") {\n const result = parseScannedUR(input);\n return Array.isArray(result) ? result : [result];\n }\n // Raw base58 xpub string — @scure/bip32 handles decoding directly\n const hdKey = HDKey.fromExtendedKey(input.trim());\n return [\n {\n hdKey,\n type: \"xpub\",\n purpose: undefined,\n coinType: undefined,\n sourceFingerprint: undefined,\n raw: input.trim(),\n },\n ];\n}\n","import { keccak_256 } from \"@noble/hashes/sha3.js\";\nimport * as secp from \"@noble/secp256k1\";\n\nexport function pubKeyToEthAddress(compressedPubKey: Uint8Array): string {\n const uncompressed = secp.Point.fromBytes(compressedPubKey).toBytes(false);\n const hash = keccak_256(uncompressed.slice(1));\n const hex = [...hash.slice(12)]\n .map((b: number) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n return toChecksumAddress(hex);\n}\n\nfunction toChecksumAddress(hex: string): string {\n const checksumHash = keccak_256(new TextEncoder().encode(hex));\n return (\n \"0x\" +\n [...hex]\n .map((c, i) => {\n if (c >= \"0\" && c <= \"9\") return c;\n const nibble =\n i % 2 === 0\n ? (checksumHash[Math.floor(i / 2)] >> 4) & 0xf\n : checksumHash[Math.floor(i / 2)] & 0xf;\n return nibble >= 8 ? c.toUpperCase() : c.toLowerCase();\n })\n .join(\"\")\n );\n}\n","import type { HDKey } from \"@scure/bip32\";\n\nimport type { ParsedXpub } from \"../parseXpub.js\";\nimport { pubKeyToEthAddress } from \"./address.js\";\n\nexport interface DerivedAccount {\n address: string;\n publicKey: string;\n /** source-fingerprint from the scanned xpub — required by Shell for signing */\n sourceFingerprint: number | undefined;\n}\n\n// Derive the first external address (index 0) from an account-level xpub.\n// Account-level xpub is at depth 3 (m/purpose'/coin'/account').\n// External chain is child 0, then address index 0.\nfunction firstChild(accountKey: HDKey): HDKey {\n return accountKey.deriveChild(0).deriveChild(0);\n}\n\nfunction toHex(bytes: Uint8Array): string {\n return [...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function deriveEvmAccount(parsed: ParsedXpub[]): DerivedAccount | undefined {\n for (const entry of parsed) {\n const { hdKey, purpose, coinType, type, sourceFingerprint } = entry;\n const isEvm =\n (purpose === 44 && coinType === 60) || (purpose === undefined && type === \"xpub\");\n\n if (!isEvm) continue;\n\n const child = firstChild(hdKey);\n if (!child.publicKey) continue;\n\n return {\n address: pubKeyToEthAddress(child.publicKey),\n publicKey: toHex(child.publicKey),\n sourceFingerprint,\n };\n }\n return undefined;\n}\n","import { parseXpub } from \"./parseXpub.js\";\nimport { deriveEvmAccount } from \"./eth/deriveAccount.js\";\nimport type { Account, Chain, EvmAccount, QRKitConfig, ScannedUR } from \"./types.js\";\n\nconst ALL_CHAINS: Chain[] = [\"evm\", \"btc\"];\n\n/**\n * Parse a connection QR (crypto-hdkey or crypto-account) and return\n * only the accounts for the chains configured in QRKitConfig.\n *\n * A dApp configured with `chains: [\"evm\"]` will never see BTC accounts,\n * and vice versa. Both chains can be enabled with `chains: [\"evm\", \"btc\"]`.\n * If `chains` is omitted, all supported chains are tried.\n */\nexport function parseConnection(\n scannedUR: ScannedUR,\n config: QRKitConfig = {},\n): Account[] {\n const chains = config.chains ?? ALL_CHAINS;\n const parsed = parseXpub(scannedUR);\n const accounts: Account[] = [];\n\n if (chains.includes(\"evm\")) {\n const account = deriveEvmAccount(parsed);\n if (account) {\n accounts.push({ chain: \"evm\", ...account } satisfies EvmAccount);\n }\n }\n\n // \"btc\" — will be implemented in src/btc/ when Bitcoin support is added.\n\n return accounts;\n}\n","// Minimal CBOR encoder.\n// Supports only the types needed for eth-sign-request: uint, bytes, text, array, map (integer keys), bool, tag.\n\nfunction majorType(major: number, n: number): number[] {\n const base = major << 5;\n if (n <= 23) return [base | n];\n if (n <= 0xff) return [base | 0x18, n];\n if (n <= 0xffff) return [base | 0x19, (n >> 8) & 0xff, n & 0xff];\n return [base | 0x1a, (n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];\n}\n\nexport function encodeItem(value: unknown): number[] {\n if (typeof value === \"boolean\") {\n return [value ? 0xf5 : 0xf4];\n }\n if (typeof value === \"number\") {\n return majorType(0, value);\n }\n if (value instanceof Uint8Array) {\n return [...majorType(2, value.length), ...value];\n }\n if (typeof value === \"string\") {\n const bytes = new TextEncoder().encode(value);\n return [...majorType(3, bytes.length), ...bytes];\n }\n if (Array.isArray(value)) {\n return [...majorType(4, value.length), ...value.flatMap(encodeItem)];\n }\n if (value instanceof CborTag) {\n return [...majorType(6, value.tag), ...encodeItem(value.value)];\n }\n if (value instanceof Map) {\n const entries = [...value.entries()];\n return [\n ...majorType(5, entries.length),\n ...entries.flatMap(([k, v]) => [...encodeItem(k), ...encodeItem(v)]),\n ];\n }\n throw new Error(`Unsupported CBOR value: ${typeof value}`);\n}\n\nexport class CborTag {\n tag: number;\n value: unknown;\n constructor(tag: number, value: unknown) {\n this.tag = tag;\n this.value = value;\n }\n}\n\nexport function encode(value: unknown): Uint8Array {\n return new Uint8Array(encodeItem(value));\n}\n","import { UR, UrFountainEncoder } from \"@qrkit/bc-ur\";\n\nexport function encodeURParts(\n cbor: Uint8Array,\n type: string,\n maxFragmentLength = 200,\n): string[] {\n const ur = UR.fromCbor({ type, payload: cbor });\n const encoder = new UrFountainEncoder(ur, maxFragmentLength);\n return encoder.getAllPartsUr().map((part) => part.toString().toUpperCase());\n}\n","import { encode, CborTag } from \"../cbor.js\";\nimport { encodeURParts } from \"../urEncoding.js\";\n\n// EIP-191 personal_sign = eth-raw-bytes (ERC-4527 data type 3)\nconst DATA_TYPE_ETH_RAW_BYTES = 3;\n\n// CBOR tag for crypto-keypath\nconst TAG_KEYPATH = 304;\n\nfunction randomBytes(n: number): Uint8Array {\n const buf = new Uint8Array(n);\n crypto.getRandomValues(buf);\n return buf;\n}\n\nfunction buildKeypath(\n purpose: number,\n coinType: number,\n sourceFingerprint: number | undefined,\n): CborTag {\n // m/purpose'/coinType'/0'/0/0\n const components = [purpose, true, coinType, true, 0, true, 0, false, 0, false];\n const keypathMap = new Map<number, unknown>([[1, components]]);\n if (sourceFingerprint !== undefined) {\n keypathMap.set(2, sourceFingerprint);\n }\n return new CborTag(TAG_KEYPATH, keypathMap);\n}\n\nfunction buildEthSignRequestCbor(\n message: string,\n address: string,\n sourceFingerprint: number | undefined,\n origin = \"qrkit\",\n): Uint8Array {\n const requestId = randomBytes(16);\n const messageBytes = new TextEncoder().encode(message);\n const keypath = buildKeypath(44, 60, sourceFingerprint);\n\n const addrHex = address.replace(/^0x/i, \"\");\n const addrBytes = new Uint8Array(addrHex.match(/.{2}/g)!.map((b) => parseInt(b, 16)));\n\n const map = new Map<number, unknown>([\n [1, new CborTag(37, requestId)], // request-id: uuid = #6.37(bstr)\n [2, messageBytes], // sign-data\n [3, DATA_TYPE_ETH_RAW_BYTES], // data-type: eth-raw-bytes for EIP-191\n [5, keypath], // derivation-path\n [6, addrBytes], // address\n [7, origin], // origin\n ]);\n\n return encode(map);\n}\n\nexport function buildEthSignRequestURParts(\n message: string,\n address: string,\n sourceFingerprint: number | undefined,\n origin?: string,\n): string[] {\n return encodeURParts(\n buildEthSignRequestCbor(message, address, sourceFingerprint, origin),\n \"eth-sign-request\",\n );\n}\n\nexport function buildEthSignRequestUR(\n message: string,\n address: string,\n sourceFingerprint: number | undefined,\n origin?: string,\n): string {\n return encodeURParts(\n buildEthSignRequestCbor(message, address, sourceFingerprint, origin),\n \"eth-sign-request\",\n )[0];\n}\n","import { decode as cborDecode, type TagDecoder } from \"cborg\";\n\nimport type { ScannedUR } from \"../types.js\";\n\nexport function parseEthSignature(scanned: ScannedUR): string {\n if (scanned.type !== \"eth-signature\") {\n throw new Error(`Expected eth-signature, got: ${scanned.type}`);\n }\n\n const map = cborDecode(scanned.cbor, {\n useMaps: true,\n tags: Object.assign([] as TagDecoder[], { 37: (v: unknown) => v }),\n }) as Map<number, unknown>;\n\n const sigBytes = map.get(2) as Uint8Array | undefined;\n if (!sigBytes || sigBytes.length < 64) {\n throw new Error(\"Invalid or missing signature bytes\");\n }\n\n return \"0x\" + [...sigBytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n"],"mappings":"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","names":["cborDecode"]}
|
|
1
|
+
{"version":3,"sources":["../src/parseXpub.ts","../src/eth/address.ts","../src/eth/deriveAccount.ts","../src/parseConnection.ts","../src/cbor.ts","../src/urEncoding.ts","../src/eth/signRequest.ts","../src/eth/signature.ts"],"sourcesContent":["import { HDKey } from \"@scure/bip32\";\nimport { decode as cborDecode, type TagDecoder } from \"cborg\";\n\nimport type { ScannedUR } from \"./types.js\";\n\nexport type XpubType = \"xpub\";\n\nexport interface ParsedXpub {\n hdKey: HDKey;\n type: XpubType;\n /** BIP-44 purpose index: 44 for EVM */\n purpose: number | undefined;\n /** BIP-44 coin type: 60 = ETH */\n coinType: number | undefined;\n /** source-fingerprint from the origin keypath — required by Shell for signing */\n sourceFingerprint: number | undefined;\n /** device or key name from crypto-hdkey key 9, set by some wallets (e.g. Keystone) */\n name?: string;\n raw: string;\n}\n\nfunction get(m: unknown, k: number): unknown {\n if (m instanceof Map) return (m as Map<number, unknown>).get(k);\n return undefined;\n}\n\nconst passthrough = (v: unknown) => v;\n\nfunction decodeCbor(cbor: Uint8Array): Map<number, unknown> {\n return cborDecode(cbor, {\n useMaps: true,\n tags: Object.assign([] as TagDecoder[], {\n 303: passthrough, // crypto-hdkey\n 304: passthrough, // crypto-keypath\n 305: passthrough, // crypto-coin-info\n }),\n }) as Map<number, unknown>;\n}\n\nfunction parseCryptoHdKey(map: unknown, raw: string): ParsedXpub {\n const keyData = get(map, 3) as Uint8Array | undefined;\n const chainCode = get(map, 4) as Uint8Array | undefined;\n\n if (!keyData || !chainCode) {\n throw new Error(\"crypto-hdkey missing key-data or chain-code\");\n }\n\n let purpose: number | undefined;\n let coinType: number | undefined;\n let sourceFingerprint: number | undefined;\n const origin = get(map, 6);\n if (origin) {\n const components = get(origin, 1);\n if (Array.isArray(components)) {\n if (components.length >= 1) purpose = components[0] as number;\n if (components.length >= 3) coinType = components[2] as number;\n }\n sourceFingerprint = get(origin, 2) as number | undefined;\n }\n\n const name = get(map, 9) as string | undefined;\n\n const hdKey = new HDKey({ publicKey: keyData, chainCode });\n return { hdKey, type: \"xpub\", purpose, coinType, sourceFingerprint, name, raw };\n}\n\nfunction parseScannedUR(scanned: ScannedUR): ParsedXpub | ParsedXpub[] {\n const { type, cbor } = scanned;\n const raw = `ur:${type}`;\n\n if (type !== \"crypto-hdkey\" && type !== \"crypto-account\") {\n throw new Error(`Unsupported UR type: ${type}`);\n }\n\n const map = decodeCbor(cbor);\n\n if (type === \"crypto-hdkey\") {\n return parseCryptoHdKey(map, raw);\n }\n\n const accounts = map.get(2) as unknown[] | undefined;\n if (!Array.isArray(accounts) || accounts.length === 0) {\n throw new Error(\"crypto-account contains no keys\");\n }\n return accounts.map((entry) => parseCryptoHdKey(entry, raw));\n}\n\nexport function parseXpub(input: ScannedUR | string): ParsedXpub[] {\n if (typeof input !== \"string\") {\n const result = parseScannedUR(input);\n return Array.isArray(result) ? result : [result];\n }\n // Raw base58 xpub string — @scure/bip32 handles decoding directly\n const hdKey = HDKey.fromExtendedKey(input.trim());\n return [\n {\n hdKey,\n type: \"xpub\",\n purpose: undefined,\n coinType: undefined,\n sourceFingerprint: undefined,\n raw: input.trim(),\n },\n ];\n}\n","import { keccak_256 } from \"@noble/hashes/sha3.js\";\nimport * as secp from \"@noble/secp256k1\";\n\nexport function pubKeyToEthAddress(compressedPubKey: Uint8Array): string {\n const uncompressed = secp.Point.fromBytes(compressedPubKey).toBytes(false);\n const hash = keccak_256(uncompressed.slice(1));\n const hex = [...hash.slice(12)]\n .map((b: number) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n return toChecksumAddress(hex);\n}\n\nfunction toChecksumAddress(hex: string): string {\n const checksumHash = keccak_256(new TextEncoder().encode(hex));\n return (\n \"0x\" +\n [...hex]\n .map((c, i) => {\n if (c >= \"0\" && c <= \"9\") return c;\n const nibble =\n i % 2 === 0\n ? (checksumHash[Math.floor(i / 2)] >> 4) & 0xf\n : checksumHash[Math.floor(i / 2)] & 0xf;\n return nibble >= 8 ? c.toUpperCase() : c.toLowerCase();\n })\n .join(\"\")\n );\n}\n","import type { HDKey } from \"@scure/bip32\";\n\nimport type { ParsedXpub } from \"../parseXpub.js\";\nimport { pubKeyToEthAddress } from \"./address.js\";\n\nexport interface DerivedAccount {\n address: string;\n publicKey: string;\n /** source-fingerprint from the scanned xpub — required by Shell for signing */\n sourceFingerprint: number | undefined;\n /** device or key name as reported by the hardware wallet, if available */\n device: string | undefined;\n}\n\n// Derive the first external address (index 0) from an account-level xpub.\n// Account-level xpub is at depth 3 (m/purpose'/coin'/account').\n// External chain is child 0, then address index 0.\nfunction firstChild(accountKey: HDKey): HDKey {\n return accountKey.deriveChild(0).deriveChild(0);\n}\n\nfunction toHex(bytes: Uint8Array): string {\n return [...bytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function deriveEvmAccount(parsed: ParsedXpub[]): DerivedAccount | undefined {\n for (const entry of parsed) {\n const { hdKey, purpose, coinType, type, sourceFingerprint, name } = entry;\n const isEvm =\n (purpose === 44 && coinType === 60) || (purpose === undefined && type === \"xpub\");\n\n if (!isEvm) continue;\n\n const child = firstChild(hdKey);\n if (!child.publicKey) continue;\n\n return {\n address: pubKeyToEthAddress(child.publicKey),\n publicKey: toHex(child.publicKey),\n sourceFingerprint,\n device: name,\n };\n }\n return undefined;\n}\n","import { parseXpub } from \"./parseXpub.js\";\nimport { deriveEvmAccount } from \"./eth/deriveAccount.js\";\nimport type { Account, Chain, EvmAccount, QRKitConfig, ScannedUR } from \"./types.js\";\n\nconst ALL_CHAINS: Chain[] = [\"evm\", \"btc\"];\n\n/**\n * Parse a connection QR (crypto-hdkey or crypto-account) and return\n * only the accounts for the chains configured in QRKitConfig.\n *\n * A dApp configured with `chains: [\"evm\"]` will never see BTC accounts,\n * and vice versa. Both chains can be enabled with `chains: [\"evm\", \"btc\"]`.\n * If `chains` is omitted, all supported chains are tried.\n */\nexport function parseConnection(\n scannedUR: ScannedUR,\n config: QRKitConfig = {},\n): Account[] {\n const chains = config.chains ?? ALL_CHAINS;\n const parsed = parseXpub(scannedUR);\n const accounts: Account[] = [];\n\n if (chains.includes(\"evm\")) {\n const account = deriveEvmAccount(parsed);\n if (account) {\n accounts.push({ chain: \"evm\", ...account } satisfies EvmAccount);\n }\n }\n\n // \"btc\" — will be implemented in src/btc/ when Bitcoin support is added.\n\n return accounts;\n}\n","// Minimal CBOR encoder.\n// Supports only the types needed for eth-sign-request: uint, bytes, text, array, map (integer keys), bool, tag.\n\nfunction majorType(major: number, n: number): number[] {\n const base = major << 5;\n if (n <= 23) return [base | n];\n if (n <= 0xff) return [base | 0x18, n];\n if (n <= 0xffff) return [base | 0x19, (n >> 8) & 0xff, n & 0xff];\n return [base | 0x1a, (n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];\n}\n\nexport function encodeItem(value: unknown): number[] {\n if (typeof value === \"boolean\") {\n return [value ? 0xf5 : 0xf4];\n }\n if (typeof value === \"number\") {\n return majorType(0, value);\n }\n if (value instanceof Uint8Array) {\n return [...majorType(2, value.length), ...value];\n }\n if (typeof value === \"string\") {\n const bytes = new TextEncoder().encode(value);\n return [...majorType(3, bytes.length), ...bytes];\n }\n if (Array.isArray(value)) {\n return [...majorType(4, value.length), ...value.flatMap(encodeItem)];\n }\n if (value instanceof CborTag) {\n return [...majorType(6, value.tag), ...encodeItem(value.value)];\n }\n if (value instanceof Map) {\n const entries = [...value.entries()];\n return [\n ...majorType(5, entries.length),\n ...entries.flatMap(([k, v]) => [...encodeItem(k), ...encodeItem(v)]),\n ];\n }\n throw new Error(`Unsupported CBOR value: ${typeof value}`);\n}\n\nexport class CborTag {\n tag: number;\n value: unknown;\n constructor(tag: number, value: unknown) {\n this.tag = tag;\n this.value = value;\n }\n}\n\nexport function encode(value: unknown): Uint8Array {\n return new Uint8Array(encodeItem(value));\n}\n","import { UR, UrFountainEncoder } from \"@qrkit/bc-ur\";\n\nexport function encodeURParts(\n cbor: Uint8Array,\n type: string,\n maxFragmentLength = 200,\n): string[] {\n const ur = UR.fromCbor({ type, payload: cbor });\n const encoder = new UrFountainEncoder(ur, maxFragmentLength);\n return encoder.getAllPartsUr().map((part) => part.toString().toUpperCase());\n}\n","import { encode, CborTag } from \"../cbor.js\";\nimport { encodeURParts } from \"../urEncoding.js\";\n\n// ERC-4527 eth-sign-request data types\nexport const EthDataType = {\n /** Legacy transaction (RLP-encoded). Wallet applies EIP-155: v = 35 + 2*chainId + recId */\n LegacyTransaction: 1,\n /** EIP-712 typed data bytes. Wallet hashes internally. */\n TypedData: 2,\n /** Personal message (EIP-191). Wallet prepends \"\\x19Ethereum Signed Message:\\n{len}\". */\n PersonalMessage: 3,\n /** EIP-1559 transaction (RLP-encoded). Wallet uses v = recId. */\n TypedTransaction: 4,\n} as const;\n\nexport type EthDataTypeValue = (typeof EthDataType)[keyof typeof EthDataType];\n\n// CBOR tag for crypto-keypath\nconst TAG_KEYPATH = 304;\n\nfunction randomBytes(n: number): Uint8Array {\n const buf = new Uint8Array(n);\n crypto.getRandomValues(buf);\n return buf;\n}\n\nfunction buildKeypath(\n purpose: number,\n coinType: number,\n sourceFingerprint: number | undefined,\n): CborTag {\n // m/purpose'/coinType'/0'/0/0\n const components = [purpose, true, coinType, true, 0, true, 0, false, 0, false];\n const keypathMap = new Map<number, unknown>([[1, components]]);\n if (sourceFingerprint !== undefined) {\n keypathMap.set(2, sourceFingerprint);\n }\n return new CborTag(TAG_KEYPATH, keypathMap);\n}\n\nexport interface EthSignRequestParams {\n /** Raw sign data. For PersonalMessage, a string is UTF-8 encoded automatically. */\n signData: Uint8Array | string;\n /** ERC-4527 data type. Defaults to PersonalMessage (3). Use EthDataType constants. */\n dataType?: number;\n address: string;\n sourceFingerprint: number | undefined;\n /** Chain ID — required by the wallet for v-value encoding on LegacyTransaction (type 1). */\n chainId?: number;\n origin?: string;\n}\n\nfunction buildEthSignRequestCbor(params: EthSignRequestParams): Uint8Array {\n const {\n signData,\n dataType = EthDataType.PersonalMessage,\n address,\n sourceFingerprint,\n chainId,\n origin = \"qrkit\",\n } = params;\n\n const requestId = randomBytes(16);\n const signBytes =\n typeof signData === \"string\" ? new TextEncoder().encode(signData) : signData;\n const keypath = buildKeypath(44, 60, sourceFingerprint);\n\n const addrHex = address.replace(/^0x/i, \"\");\n const addrBytes = new Uint8Array(addrHex.match(/.{2}/g)!.map((b) => parseInt(b, 16)));\n\n // Keys must be in strictly ascending order for zcbor (Shell firmware decoder).\n const map = new Map<number, unknown>([\n [1, new CborTag(37, requestId)], // request-id: uuid = #6.37(bstr)\n [2, signBytes], // sign-data\n [3, dataType], // data-type\n ]);\n\n if (chainId !== undefined) {\n map.set(4, chainId); // chain-id — must come before key 5\n }\n\n map.set(5, keypath); // derivation-path\n map.set(6, addrBytes); // address\n map.set(7, origin); // origin\n\n return encode(map);\n}\n\nexport function buildEthSignRequestURParts(params: EthSignRequestParams): string[] {\n return encodeURParts(buildEthSignRequestCbor(params), \"eth-sign-request\");\n}\n\nexport function buildEthSignRequestUR(params: EthSignRequestParams): string {\n return encodeURParts(buildEthSignRequestCbor(params), \"eth-sign-request\")[0];\n}\n","import { decode as cborDecode, type TagDecoder } from \"cborg\";\n\nimport type { ScannedUR } from \"../types.js\";\n\nexport function parseEthSignature(scanned: ScannedUR): string {\n if (scanned.type !== \"eth-signature\") {\n throw new Error(`Expected eth-signature, got: ${scanned.type}`);\n }\n\n const map = cborDecode(scanned.cbor, {\n useMaps: true,\n tags: Object.assign([] as TagDecoder[], { 37: (v: unknown) => v }),\n }) as Map<number, unknown>;\n\n const sigBytes = map.get(2) as Uint8Array | undefined;\n if (!sigBytes || sigBytes.length < 64) {\n throw new Error(\"Invalid or missing signature bytes\");\n }\n\n return \"0x\" + [...sigBytes].map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n"],"mappings":"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","names":["cborDecode"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qrkit/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Protocol core for QR-based airgapped wallet flows — UR decoding, xpub parsing, address derivation, sign request and signature handling.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|