@ckb-ccc/core 1.11.4 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/ckb/transaction.d.ts +160 -29
  3. package/dist/ckb/transaction.d.ts.map +1 -1
  4. package/dist/ckb/transaction.js +185 -39
  5. package/dist/client/jsonRpc/client.d.ts +1 -1
  6. package/dist/client/jsonRpc/client.d.ts.map +1 -1
  7. package/dist/client/jsonRpc/client.js +4 -1
  8. package/dist/client/jsonRpc/transformers.d.ts +1 -1
  9. package/dist/client/jsonRpc/transformers.d.ts.map +1 -1
  10. package/dist/client/jsonRpc/transformers.js +2 -1
  11. package/dist/jsonRpc/requestor.js +1 -1
  12. package/dist/molecule/entity.d.ts +3 -3
  13. package/dist/molecule/entity.d.ts.map +1 -1
  14. package/dist/molecule/entity.js +1 -1
  15. package/dist/signer/btc/verify.d.ts +21 -0
  16. package/dist/signer/btc/verify.d.ts.map +1 -1
  17. package/dist/signer/btc/verify.js +38 -2
  18. package/dist/signer/doge/signerDogePrivateKey.d.ts.map +1 -1
  19. package/dist/signer/doge/signerDogePrivateKey.js +2 -2
  20. package/dist/signer/doge/verify.d.ts +12 -1
  21. package/dist/signer/doge/verify.d.ts.map +1 -1
  22. package/dist/signer/doge/verify.js +15 -5
  23. package/dist/signer/nostr/verify.js +4 -4
  24. package/dist/signer/signer/index.d.ts +14 -5
  25. package/dist/signer/signer/index.d.ts.map +1 -1
  26. package/dist/signer/signer/index.js +19 -11
  27. package/dist.commonjs/ckb/transaction.d.ts +160 -29
  28. package/dist.commonjs/ckb/transaction.d.ts.map +1 -1
  29. package/dist.commonjs/ckb/transaction.js +187 -40
  30. package/dist.commonjs/client/jsonRpc/client.d.ts +1 -1
  31. package/dist.commonjs/client/jsonRpc/client.d.ts.map +1 -1
  32. package/dist.commonjs/client/jsonRpc/client.js +4 -1
  33. package/dist.commonjs/client/jsonRpc/transformers.d.ts +1 -1
  34. package/dist.commonjs/client/jsonRpc/transformers.d.ts.map +1 -1
  35. package/dist.commonjs/client/jsonRpc/transformers.js +2 -1
  36. package/dist.commonjs/jsonRpc/requestor.js +1 -1
  37. package/dist.commonjs/molecule/entity.d.ts +3 -3
  38. package/dist.commonjs/molecule/entity.d.ts.map +1 -1
  39. package/dist.commonjs/molecule/entity.js +1 -1
  40. package/dist.commonjs/signer/btc/verify.d.ts +21 -0
  41. package/dist.commonjs/signer/btc/verify.d.ts.map +1 -1
  42. package/dist.commonjs/signer/btc/verify.js +40 -2
  43. package/dist.commonjs/signer/doge/signerDogePrivateKey.d.ts.map +1 -1
  44. package/dist.commonjs/signer/doge/signerDogePrivateKey.js +2 -2
  45. package/dist.commonjs/signer/doge/verify.d.ts +12 -1
  46. package/dist.commonjs/signer/doge/verify.d.ts.map +1 -1
  47. package/dist.commonjs/signer/doge/verify.js +15 -4
  48. package/dist.commonjs/signer/nostr/verify.js +5 -5
  49. package/dist.commonjs/signer/signer/index.d.ts +14 -5
  50. package/dist.commonjs/signer/signer/index.d.ts.map +1 -1
  51. package/dist.commonjs/signer/signer/index.js +21 -13
  52. package/package.json +3 -4
  53. package/{prettier.config.mjs → prettier.config.cjs} +2 -4
  54. package/src/ckb/transaction.ts +254 -80
  55. package/src/client/jsonRpc/client.ts +5 -2
  56. package/src/client/jsonRpc/transformers.ts +6 -1
  57. package/src/jsonRpc/requestor.ts +2 -2
  58. package/src/molecule/entity.ts +10 -9
  59. package/src/signer/btc/verify.ts +52 -3
  60. package/src/signer/doge/signerDogePrivateKey.ts +2 -2
  61. package/src/signer/doge/verify.ts +23 -7
  62. package/src/signer/nostr/verify.ts +4 -4
  63. package/src/signer/signer/index.ts +20 -10
  64. /package/{vitest.config.ts → vitest.config.mts} +0 -0
@@ -198,7 +198,7 @@ export class OutPoint extends mol.Entity.Base<OutPointLike, OutPoint>() {
198
198
  * @public
199
199
  */
200
200
  export type CellOutputLike = {
201
- capacity: NumLike;
201
+ capacity?: NumLike | null;
202
202
  lock: ScriptLike;
203
203
  type?: ScriptLike | null;
204
204
  };
@@ -238,7 +238,6 @@ export class CellOutput extends mol.Entity.Base<CellOutputLike, CellOutput>() {
238
238
  * This method supports automatic capacity calculation when capacity is 0 or omitted.
239
239
  *
240
240
  * @param cellOutput - A CellOutputLike object or an instance of CellOutput.
241
- * Can also be an object without capacity when outputData is provided.
242
241
  * @param outputData - Optional output data used for automatic capacity calculation.
243
242
  * When provided and capacity is 0, the capacity will be calculated
244
243
  * as occupiedSize + outputData.length.
@@ -259,16 +258,9 @@ export class CellOutput extends mol.Entity.Base<CellOutputLike, CellOutput>() {
259
258
  * }, "0x1234"); // Capacity will be calculated automatically
260
259
  * ```
261
260
  */
262
- static from(cellOutput: CellOutputLike, outputData?: HexLike): CellOutput;
263
261
  static from(
264
- cellOutput: Omit<CellOutputLike, "capacity"> &
265
- Partial<Pick<CellOutputLike, "capacity">>,
266
- outputData: HexLike,
267
- ): CellOutput;
268
- static from(
269
- cellOutput: Omit<CellOutputLike, "capacity"> &
270
- Partial<Pick<CellOutputLike, "capacity">>,
271
- outputData?: HexLike,
262
+ cellOutput: CellOutputLike,
263
+ outputData?: HexLike | null,
272
264
  ): CellOutput {
273
265
  if (cellOutput instanceof CellOutput) {
274
266
  return cellOutput;
@@ -307,34 +299,186 @@ export const CellOutputVec = mol.vector(CellOutput);
307
299
 
308
300
  /**
309
301
  * @public
302
+ * Represents a cell-like object that may or may not be on-chain.
303
+ * It can optionally have an `outPoint` (or `previousOutput`).
304
+ * This is used as a flexible input for creating `CellAny` instances.
305
+ * @see CellAny
310
306
  */
311
- export type CellLike = (
312
- | {
313
- outPoint: OutPointLike;
314
- }
315
- | { previousOutput: OutPointLike }
316
- ) & {
307
+ export type CellAnyLike = {
308
+ outPoint?: OutPointLike | null;
309
+ previousOutput?: OutPointLike | null;
317
310
  cellOutput: CellOutputLike;
318
- outputData: HexLike;
311
+ outputData?: HexLike | null;
319
312
  };
320
313
  /**
314
+ * Represents a CKB cell that can be either on-chain (with an `outPoint`) or off-chain (without an `outPoint`).
315
+ * This class provides a unified interface for handling cells before they are included in a transaction,
316
+ * or for cells that are already part of the blockchain state.
317
+ *
321
318
  * @public
322
319
  */
323
- export class Cell {
320
+ export class CellAny {
324
321
  /**
325
- * Creates an instance of Cell.
322
+ * Creates an instance of CellAny.
326
323
  *
327
- * @param outPoint - The output point of the cell.
328
324
  * @param cellOutput - The cell output of the cell.
329
325
  * @param outputData - The output data of the cell.
326
+ * @param outPoint - The optional output point of the cell. If provided, the cell is considered on-chain.
330
327
  */
331
328
 
332
329
  constructor(
333
- public outPoint: OutPoint,
334
330
  public cellOutput: CellOutput,
335
331
  public outputData: Hex,
332
+ public outPoint?: OutPoint,
336
333
  ) {}
337
334
 
335
+ /**
336
+ * Creates a `CellAny` instance from a `CellAnyLike` object.
337
+ * This factory method provides a convenient way to create `CellAny` instances
338
+ * from plain objects, automatically handling the optional `outPoint` or `previousOutput`.
339
+ *
340
+ * @param cell - A `CellAnyLike` object.
341
+ * @returns A new `CellAny` instance.
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * // Create an off-chain cell (e.g., a new output)
346
+ * const offChainCell = CellAny.from({
347
+ * cellOutput: { capacity: 1000n, lock: lockScript },
348
+ * outputData: "0x"
349
+ * });
350
+ *
351
+ * // Create an on-chain cell from an input
352
+ * const onChainCell = CellAny.from({
353
+ * outPoint: { txHash: "0x...", index: 0 },
354
+ * cellOutput: { capacity: 2000n, lock: lockScript },
355
+ * outputData: "0x1234"
356
+ * });
357
+ * ```
358
+ */
359
+ static from(cell: CellAnyLike): CellAny {
360
+ if (cell instanceof CellAny) {
361
+ return cell;
362
+ }
363
+
364
+ return new CellAny(
365
+ CellOutput.from(cell.cellOutput, cell.outputData),
366
+ hexFrom(cell.outputData ?? "0x"),
367
+ apply(OutPoint.from, cell.outPoint ?? cell.previousOutput),
368
+ );
369
+ }
370
+
371
+ /**
372
+ * Calculates the total occupied size of the cell in bytes.
373
+ * This includes the size of the `CellOutput` structure plus the size of the `outputData`.
374
+ *
375
+ * @returns The total occupied size in bytes.
376
+ */
377
+ get occupiedSize() {
378
+ return this.cellOutput.occupiedSize + bytesFrom(this.outputData).byteLength;
379
+ }
380
+
381
+ /**
382
+ * Calculates the free capacity of the cell.
383
+ * Free capacity is the total capacity minus the capacity occupied by the cell's structure and data.
384
+ *
385
+ * @returns The free capacity in shannons as a `Num`.
386
+ */
387
+ get capacityFree() {
388
+ return this.cellOutput.capacity - fixedPointFrom(this.occupiedSize);
389
+ }
390
+
391
+ /**
392
+ * Checks if the cell is a Nervos DAO cell and optionally checks its phase.
393
+ *
394
+ * @param client - A CKB client instance to fetch known script information.
395
+ * @param phase - Optional phase to check: "deposited" or "withdrew".
396
+ * If omitted, it checks if the cell is a DAO cell regardless of phase.
397
+ * @returns A promise that resolves to `true` if the cell is a matching Nervos DAO cell, `false` otherwise.
398
+ */
399
+ async isNervosDao(
400
+ client: Client,
401
+ phase?: "deposited" | "withdrew",
402
+ ): Promise<boolean> {
403
+ const { type } = this.cellOutput;
404
+
405
+ const daoType = await client.getKnownScript(KnownScript.NervosDao);
406
+ if (
407
+ !type ||
408
+ type.codeHash !== daoType.codeHash ||
409
+ type.hashType !== daoType.hashType
410
+ ) {
411
+ // Non Nervos DAO cell
412
+ return false;
413
+ }
414
+
415
+ const hasWithdrew = numFrom(this.outputData) !== Zero;
416
+ return (
417
+ !phase ||
418
+ (phase === "deposited" && !hasWithdrew) ||
419
+ (phase === "withdrew" && hasWithdrew)
420
+ );
421
+ }
422
+
423
+ /**
424
+ * Clones the `CellAny` instance.
425
+ *
426
+ * @returns A new `CellAny` instance that is a deep copy of the current one.
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const clonedCell = cellAny.clone();
431
+ * ```
432
+ */
433
+ clone(): CellAny {
434
+ return new CellAny(
435
+ this.cellOutput.clone(),
436
+ this.outputData,
437
+ this.outPoint?.clone(),
438
+ );
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Represents a cell-like object that is guaranteed to be on-chain.
444
+ * It must have an `outPoint` (or its alias `previousOutput`).
445
+ * This is used as a type constraint for creating `Cell` instances.
446
+ * @see Cell
447
+ * @public
448
+ */
449
+ export type CellLike = CellAnyLike &
450
+ (
451
+ | {
452
+ outPoint: OutPointLike;
453
+ previousOutput?: undefined | null;
454
+ }
455
+ | {
456
+ outPoint?: undefined | null;
457
+ previousOutput: OutPointLike;
458
+ }
459
+ );
460
+ /**
461
+ * Represents an on-chain CKB cell, which is a `CellAny` that is guaranteed to have an `outPoint`.
462
+ * This class is typically used for cells that are already part of the blockchain state, such as transaction inputs.
463
+ * @public
464
+ */
465
+ export class Cell extends CellAny {
466
+ /**
467
+ * Creates an instance of an on-chain Cell.
468
+ *
469
+ * @param outPoint - The output point of the cell.
470
+ * @param cellOutput - The cell output of the cell.
471
+ * @param outputData - The output data of the cell.
472
+ */
473
+
474
+ constructor(
475
+ public outPoint: OutPoint,
476
+ cellOutput: CellOutput,
477
+ outputData: Hex,
478
+ ) {
479
+ super(cellOutput, outputData, outPoint);
480
+ }
481
+
338
482
  /**
339
483
  * Creates a Cell instance from a CellLike object.
340
484
  * This method accepts either `outPoint` or `previousOutput` to specify the cell's location,
@@ -376,25 +520,10 @@ export class Cell {
376
520
  }
377
521
 
378
522
  return new Cell(
379
- OutPoint.from("outPoint" in cell ? cell.outPoint : cell.previousOutput),
523
+ OutPoint.from(cell.outPoint ?? cell.previousOutput),
380
524
  CellOutput.from(cell.cellOutput, cell.outputData),
381
- hexFrom(cell.outputData),
382
- );
383
- }
384
-
385
- get capacityFree() {
386
- const occupiedSize = fixedPointFrom(
387
- this.cellOutput.occupiedSize + bytesFrom(this.outputData).length,
525
+ hexFrom(cell.outputData ?? "0x"),
388
526
  );
389
- return this.cellOutput.capacity - occupiedSize;
390
- }
391
-
392
- /**
393
- * Occupied bytes of a cell on chain
394
- * It's CellOutput.occupiedSize + bytesFrom(outputData).byteLength
395
- */
396
- get occupiedSize() {
397
- return this.cellOutput.occupiedSize + bytesFrom(this.outputData).byteLength;
398
527
  }
399
528
 
400
529
  /**
@@ -426,30 +555,22 @@ export class Cell {
426
555
  return calcDaoProfit(this.capacityFree, depositHeader, withdrawHeader);
427
556
  }
428
557
 
429
- async isNervosDao(
430
- client: Client,
431
- phase?: "deposited" | "withdrew",
432
- ): Promise<boolean> {
433
- const { type } = this.cellOutput;
434
-
435
- const daoType = await client.getKnownScript(KnownScript.NervosDao);
436
- if (
437
- !type ||
438
- type.codeHash !== daoType.codeHash ||
439
- type.hashType !== daoType.hashType
440
- ) {
441
- // Non Nervos DAO cell
442
- return false;
443
- }
444
-
445
- const hasWithdrew = numFrom(this.outputData) !== Zero;
446
- return (
447
- !phase ||
448
- (phase === "deposited" && !hasWithdrew) ||
449
- (phase === "withdrew" && hasWithdrew)
450
- );
451
- }
452
-
558
+ /**
559
+ * Retrieves detailed information about a Nervos DAO cell, including its deposit and withdrawal headers.
560
+ *
561
+ * @param client - A CKB client instance to fetch cell and header data.
562
+ * @returns A promise that resolves to an object containing header information.
563
+ * - If not a DAO cell, returns `{}`.
564
+ * - If a deposited DAO cell, returns `{ depositHeader }`.
565
+ * - If a withdrawn DAO cell, returns `{ depositHeader, withdrawHeader }`.
566
+ *
567
+ * @throws If the cell is a DAO cell but its corresponding headers cannot be fetched.
568
+ *
569
+ * @example
570
+ * ```typescript
571
+ * const daoInfo = await cell.getNervosDaoInfo(client);
572
+ * ```
573
+ */
453
574
  async getNervosDaoInfo(client: Client): Promise<
454
575
  // Non Nervos DAO cell
455
576
  | {
@@ -1583,39 +1704,92 @@ export class Transaction extends mol.Entity.Base<
1583
1704
  * await tx.getOutput(0);
1584
1705
  * ```
1585
1706
  */
1586
- getOutput(index: NumLike):
1587
- | {
1588
- cellOutput: CellOutput;
1589
- outputData: Hex;
1590
- }
1591
- | undefined {
1707
+ getOutput(index: NumLike): CellAny | undefined {
1592
1708
  const i = Number(numFrom(index));
1593
1709
  if (i >= this.outputs.length) {
1594
1710
  return;
1595
1711
  }
1596
- return {
1712
+ return CellAny.from({
1597
1713
  cellOutput: this.outputs[i],
1598
1714
  outputData: this.outputsData[i] ?? "0x",
1599
- };
1715
+ });
1600
1716
  }
1717
+
1601
1718
  /**
1602
- * Add output
1719
+ * Provides an iterable over the transaction's output cells.
1720
+ *
1721
+ * This getter is a convenient way to iterate through all the output cells (`CellAny`)
1722
+ * of the transaction, combining the `outputs` and `outputsData` arrays.
1723
+ * It can be used with `for...of` loops or other iterable-consuming patterns.
1603
1724
  *
1604
- * @param outputLike - The cell output to add
1605
- * @param outputData - optional output data
1725
+ * @public
1726
+ * @category Getter
1727
+ * @returns An `Iterable<CellAny>` that yields each output cell of the transaction.
1606
1728
  *
1607
1729
  * @example
1608
1730
  * ```typescript
1609
- * await tx.addOutput(cellOutput, "0xabcd");
1731
+ * for (const cell of tx.outputCells) {
1732
+ * console.log(`Output cell capacity: ${cell.cellOutput.capacity}`);
1733
+ * }
1610
1734
  * ```
1611
1735
  */
1736
+ get outputCells(): Iterable<CellAny> {
1737
+ const { outputs, outputsData } = this;
1738
+
1739
+ function* generator(): Generator<CellAny> {
1740
+ for (let i = 0; i < outputs.length; i++) {
1741
+ yield CellAny.from({
1742
+ cellOutput: outputs[i],
1743
+ outputData: outputsData[i] ?? "0x",
1744
+ });
1745
+ }
1746
+ }
1747
+
1748
+ return generator();
1749
+ }
1750
+
1751
+ /**
1752
+ * Adds an output to the transaction.
1753
+ *
1754
+ * This method supports two overloads for adding an output:
1755
+ * 1. By providing a `CellAnyLike` object, which encapsulates both `cellOutput` and `outputData`.
1756
+ * 2. By providing a `CellOutputLike` object and an optional `outputData`.
1757
+ *
1758
+ * @param cellOrOutputLike - A cell-like object containing both cell output and data, or just the cell output object.
1759
+ * @param outputDataLike - Optional data for the cell output. Defaults to "0x" if not provided in the first argument.
1760
+ * @returns The new number of outputs in the transaction.
1761
+ *
1762
+ * @example
1763
+ * ```typescript
1764
+ * // 1. Add an output using a CellAnyLike object
1765
+ * const newLength1 = tx.addOutput({
1766
+ * cellOutput: { lock: recipientLock }, // capacity is calculated automatically
1767
+ * outputData: "0x1234",
1768
+ * });
1769
+ *
1770
+ * // 2. Add an output using CellOutputLike and data separately
1771
+ * const newLength2 = tx.addOutput({ lock: recipientLock }, "0xabcd");
1772
+ * ```
1773
+ */
1774
+ addOutput(cellLike: CellAnyLike): number;
1775
+ addOutput(
1776
+ outputLike: CellOutputLike,
1777
+ outputDataLike?: HexLike | null,
1778
+ ): number;
1612
1779
  addOutput(
1613
- outputLike: Omit<CellOutputLike, "capacity"> &
1614
- Partial<Pick<CellOutputLike, "capacity">>,
1615
- outputData: HexLike = "0x",
1780
+ cellOrOutputLike: CellAnyLike | CellOutputLike,
1781
+ outputDataLike?: HexLike | null,
1616
1782
  ): number {
1617
- const len = this.outputs.push(CellOutput.from(outputLike, outputData));
1618
- this.setOutputDataAt(len - 1, outputData);
1783
+ const cell =
1784
+ "cellOutput" in cellOrOutputLike
1785
+ ? CellAny.from(cellOrOutputLike)
1786
+ : CellAny.from({
1787
+ cellOutput: cellOrOutputLike,
1788
+ outputData: outputDataLike,
1789
+ });
1790
+
1791
+ const len = this.outputs.push(cell.cellOutput);
1792
+ this.setOutputDataAt(len - 1, cell.outputData);
1619
1793
 
1620
1794
  return len;
1621
1795
  }
@@ -251,11 +251,14 @@ export abstract class ClientJsonRpc extends Client {
251
251
 
252
252
  sendTransactionNoCache = this.buildSender(
253
253
  "send_transaction",
254
- [JsonRpcTransformers.transactionFrom],
254
+ [
255
+ JsonRpcTransformers.transactionFrom,
256
+ (validator?: OutputsValidator | null) => validator ?? undefined,
257
+ ],
255
258
  hexFrom,
256
259
  ) as (
257
260
  transaction: TransactionLike,
258
- validator?: OutputsValidator | undefined,
261
+ validator?: OutputsValidator | null,
259
262
  ) => Promise<Hex>;
260
263
 
261
264
  /**
@@ -124,7 +124,12 @@ export class JsonRpcTransformers {
124
124
  since: cellInput.since,
125
125
  });
126
126
  }
127
- static cellOutputFrom(cellOutput: CellOutputLike): JsonRpcCellOutput {
127
+ static cellOutputFrom(
128
+ cellOutputLike: CellOutputLike,
129
+ outputData?: HexLike | null,
130
+ ): JsonRpcCellOutput {
131
+ const cellOutput = CellOutput.from(cellOutputLike, outputData);
132
+
128
133
  return {
129
134
  capacity: numToHex(cellOutput.capacity),
130
135
  lock: JsonRpcTransformers.scriptFrom(cellOutput.lock),
@@ -141,8 +141,8 @@ export class RequestorJsonRpc {
141
141
  if (res.id !== payload.id) {
142
142
  throw new Error(`Id mismatched, got ${res.id}, expected ${payload.id}`);
143
143
  }
144
- if (res.error) {
145
- throw res.error;
144
+ if (res.error != null) {
145
+ throw res.error as unknown;
146
146
  }
147
147
  return res.result;
148
148
  }
@@ -156,16 +156,17 @@ export function codec<
156
156
  Encodable,
157
157
  TypeLike extends Encodable,
158
158
  Decoded extends TypeLike,
159
- Type extends object & TypeLike,
160
- ConstructorType extends Constructor<Type> & {
161
- from(decoded: TypeLike): Type;
162
- byteLength?: number;
163
- encode(encodable: TypeLike): Bytes;
164
- decode(bytesLike: BytesLike): TypeLike;
165
- fromBytes(bytes: BytesLike): Type;
166
- },
167
159
  >(codec: Codec<Encodable, Decoded>) {
168
- return function (Constructor: ConstructorType) {
160
+ return function <
161
+ Type extends TypeLike,
162
+ ConstructorType extends Constructor<Type> & {
163
+ from(decoded: TypeLike): Type;
164
+ byteLength?: number;
165
+ encode(encodable: TypeLike): Bytes;
166
+ decode(bytesLike: BytesLike): Type;
167
+ fromBytes(bytes: BytesLike): Type;
168
+ },
169
+ >(Constructor: ConstructorType, ..._: unknown[]) {
169
170
  Constructor.byteLength = codec.byteLength;
170
171
  Constructor.encode = function (encodable: TypeLike) {
171
172
  return codec.encode(encodable);
@@ -1,10 +1,59 @@
1
1
  import { secp256k1 } from "@noble/curves/secp256k1";
2
2
  import { ripemd160 } from "@noble/hashes/legacy.js";
3
3
  import { sha256 } from "@noble/hashes/sha2.js";
4
- import { magicHash } from "bitcoinjs-message";
5
4
  import bs58check from "bs58check";
6
- import { Bytes, BytesLike, bytesConcat, bytesFrom } from "../../bytes/index.js";
5
+ import { Bytes, bytesConcat, bytesFrom, BytesLike } from "../../bytes/index.js";
7
6
  import { Hex, hexFrom } from "../../hex/index.js";
7
+ import { numFrom, numLeToBytes, NumLike } from "../../num/index.js";
8
+
9
+ /**
10
+ * Encodes a number into a variable-length byte array according to the Bitcoin protocol.
11
+ * This format is used for encoding lengths of data, such as script lengths.
12
+ *
13
+ * @param len - The number to encode. Can be a NumLike.
14
+ * @returns The encoded length as a Bytes.
15
+ * @public
16
+ */
17
+ export function btcVarLengthBytesFrom(len: NumLike): Bytes {
18
+ const num = numFrom(len);
19
+
20
+ return num < 0xfd
21
+ ? numLeToBytes(num, 1)
22
+ : num <= 0xffff
23
+ ? bytesConcat([0xfd], numLeToBytes(num, 2))
24
+ : num <= 0xffffffff
25
+ ? bytesConcat([0xfe], numLeToBytes(num, 4))
26
+ : bytesConcat([0xff], numLeToBytes(num, 8));
27
+ }
28
+
29
+ /**
30
+ * Computes the message hash for Bitcoin ECDSA signatures.
31
+ * This function follows the Bitcoin message signing standard, which involves
32
+ * prefixing the message with a magic string and its length, then double SHA256 hashing the result.
33
+ *
34
+ * @param message - The message to be hashed. Can be a string or BytesLike.
35
+ * @param messagePrefix - Optional. A custom prefix to use instead of the default "\u0018Bitcoin Signed Message:\n".
36
+ * @returns The Bitcoin hash of the prefixed message as Bytes.
37
+ * @public
38
+ */
39
+ export function messageHashBtcEcdsa(
40
+ message: string | BytesLike,
41
+ messagePrefix?: string | BytesLike,
42
+ ): Bytes {
43
+ const prefix = messagePrefix ?? "\u0018Bitcoin Signed Message:\n";
44
+ const rawPrefix: Bytes =
45
+ typeof prefix === "string" ? bytesFrom(prefix, "utf8") : bytesFrom(prefix);
46
+ const rawMsg: Bytes =
47
+ typeof message === "string"
48
+ ? bytesFrom(message, "utf8")
49
+ : bytesFrom(message);
50
+
51
+ return sha256(
52
+ sha256(
53
+ bytesConcat(rawPrefix, btcVarLengthBytesFrom(rawMsg.length), rawMsg),
54
+ ),
55
+ );
56
+ }
8
57
 
9
58
  /**
10
59
  * @public
@@ -47,7 +96,7 @@ export function verifyMessageBtcEcdsa(
47
96
 
48
97
  return secp256k1.verify(
49
98
  bytesFrom(rawSign),
50
- magicHash(challenge),
99
+ messageHashBtcEcdsa(challenge),
51
100
  bytesFrom(publicKey),
52
101
  );
53
102
  }
@@ -1,5 +1,4 @@
1
1
  import { secp256k1 } from "@noble/curves/secp256k1";
2
- import { magicHash } from "bitcoinjs-message";
3
2
  import {
4
3
  Bytes,
5
4
  bytesConcat,
@@ -11,6 +10,7 @@ import { Client } from "../../client/index.js";
11
10
  import { Hex, hexFrom } from "../../hex/index.js";
12
11
  import { btcP2pkhAddressFromPublicKey } from "../btc/verify.js";
13
12
  import { SignerDoge } from "./signerDoge.js";
13
+ import { messageHashDogeEcdsa } from "./verify.js";
14
14
 
15
15
  /**
16
16
  * A class extending SignerDoge that provides access to a Doge address.
@@ -85,7 +85,7 @@ export class SignerDogePrivateKey extends SignerDoge {
85
85
  const challenge = typeof msg === "string" ? msg : hexFrom(msg).slice(2);
86
86
 
87
87
  const signature = secp256k1.sign(
88
- magicHash(challenge, "\x19Dogecoin Signed Message:\n"),
88
+ messageHashDogeEcdsa(challenge),
89
89
  this.privateKey,
90
90
  );
91
91
 
@@ -1,12 +1,32 @@
1
1
  import { secp256k1 } from "@noble/curves/secp256k1";
2
- import { magicHash } from "bitcoinjs-message";
3
- import { bytesFrom, BytesLike } from "../../bytes/index.js";
2
+ import { Bytes, bytesFrom, BytesLike } from "../../bytes/index.js";
4
3
  import { hexFrom } from "../../hex/index.js";
5
4
  import {
6
5
  btcEcdsaPublicKeyHash,
7
6
  btcPublicKeyFromP2pkhAddress,
7
+ messageHashBtcEcdsa,
8
8
  } from "../btc/verify.js";
9
9
 
10
+ /**
11
+ * Computes the message hash for Dogecoin ECDSA signatures.
12
+ * This function follows the Dogecoin message signing standard, which involves
13
+ * prefixing the message with a magic string and its length, then double SHA256 hashing the result.
14
+ *
15
+ * @param message - The message to be hashed. Can be a string or BytesLike.
16
+ * @param messagePrefix - Optional. A custom prefix to use instead of the default "\x19Dogecoin Signed Message:\n".
17
+ * @returns The Dogecoin hash of the prefixed message as Bytes.
18
+ * @public
19
+ */
20
+ export function messageHashDogeEcdsa(
21
+ message: string | BytesLike,
22
+ messagePrefix?: string | BytesLike,
23
+ ): Bytes {
24
+ return messageHashBtcEcdsa(
25
+ message,
26
+ messagePrefix ?? "\x19Dogecoin Signed Message:\n",
27
+ );
28
+ }
29
+
10
30
  /**
11
31
  * @public
12
32
  */
@@ -29,11 +49,7 @@ export function verifyMessageDogeEcdsa(
29
49
  btcPublicKeyFromP2pkhAddress(address) ===
30
50
  hexFrom(
31
51
  btcEcdsaPublicKeyHash(
32
- sig
33
- .recoverPublicKey(
34
- magicHash(challenge, "\x19Dogecoin Signed Message:\n"),
35
- )
36
- .toHex(),
52
+ sig.recoverPublicKey(messageHashDogeEcdsa(challenge)).toHex(),
37
53
  ),
38
54
  )
39
55
  );
@@ -1,5 +1,5 @@
1
1
  import { schnorr } from "@noble/curves/secp256k1";
2
- import { sha256 } from "@noble/hashes/sha256";
2
+ import { sha256 } from "@noble/hashes/sha2.js";
3
3
  import { bech32 } from "bech32";
4
4
  import { Bytes, BytesLike, bytesFrom } from "../../bytes/index.js";
5
5
  import { hexFrom } from "../../hex/index.js";
@@ -59,13 +59,13 @@ export function verifyMessageNostrEvent(
59
59
  address: string,
60
60
  ): boolean {
61
61
  const { words } = bech32.decode(address);
62
- const publicKey = hexFrom(bech32.fromWords(words)).slice(2);
62
+ const pubkey = hexFrom(bech32.fromWords(words)).slice(2);
63
63
 
64
64
  const event = buildNostrEventFromMessage(message);
65
- const eventHash = nostrEventHash(event);
65
+ const eventHash = nostrEventHash({ ...event, pubkey });
66
66
 
67
67
  try {
68
- return schnorr.verify(hexFrom(signature).slice(2), eventHash, publicKey);
68
+ return schnorr.verify(hexFrom(signature).slice(2), eventHash, pubkey);
69
69
  } catch (_) {
70
70
  return false;
71
71
  }