quantumcoin 8.0.0 → 8.0.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-SDK.md CHANGED
@@ -419,6 +419,8 @@ Core signing implementation.
419
419
  - `getAddress(): Promise<string>`
420
420
  - `signTransaction(tx: TransactionRequest): Promise<string>`
421
421
  - `sendTransaction(tx: TransactionRequest): Promise<TransactionResponse>`
422
+ - `signMessageSync(message: string | Uint8Array, signingContext?: number | null): string` — signs an arbitrary message using the EIP-191 personal-message digest (see [Message signing](#message-signing-eip-191)). Returns an opaque post-quantum signature blob (0x hex) that embeds the signer's public key. The message may be at most 1 MiB once UTF-8 encoded (`INVALID_ARGUMENT` otherwise). Optional `signingContext`: omitted/`null` derives the compact context from the key type (`0` for keyType 3, `1` for keyType 5); `2` selects the full-signature scheme for a keyType 3 wallet.
423
+ - `signMessage(message: string | Uint8Array, signingContext?: number | null): Promise<string>` — async wrapper over `signMessageSync` (honors the ethers `Signer` interface contract; the underlying signing is synchronous).
422
424
 
423
425
  ### `Wallet`
424
426
 
@@ -487,6 +489,38 @@ Address-only signer.
487
489
  - `new VoidSigner(address: string, provider?: AbstractProvider)`
488
490
  - `getAddress(): Promise<string>`
489
491
 
492
+ ### Message signing (EIP-191)
493
+
494
+ Arbitrary-message signing and verification, adapted from ethers for
495
+ QuantumCoin's post-quantum cryptography.
496
+
497
+ - `Wallet.signMessage(message, signingContext?)` / `Wallet.signMessageSync(message, signingContext?)` — see [`BaseWallet`](#basewallet).
498
+ - `hashMessage(message: string | Uint8Array): string` — the EIP-191 digest,
499
+ `keccak256("\x19Ethereum Signed Message:\n" + len + message)` (32 bytes). Same
500
+ prefix as Ethereum, so it matches `personal_sign` in `quantum-coin-go`. Strings
501
+ are UTF-8 encoded; the length prefix counts message **bytes**.
502
+ - `verifyMessage(message: string | Uint8Array, signature: string | Uint8Array): string`
503
+ — **synchronous** (matches ethers; there is no `verifyMessageSync`). Returns the
504
+ recovered 32-byte signer address; throws `INVALID_ARGUMENT` if the signature is
505
+ malformed or does not verify.
506
+
507
+ ```js
508
+ const { Wallet, verifyMessage } = require("quantumcoin");
509
+ const wallet = Wallet.createRandom();
510
+ const sig = await wallet.signMessage("Hello Joe");
511
+ verifyMessage("Hello Joe", sig) === wallet.address; // true
512
+ ```
513
+
514
+ **Key differences vs Ethereum**
515
+
516
+ | Property | Ethereum | QuantumCoin |
517
+ | --- | --- | --- |
518
+ | Message prefix / hash | EIP-191 + keccak256 | Identical EIP-191 + keccak256 |
519
+ | Signature | 65-byte `(r, s, v)` | Opaque multi-KB blob (scheme id byte + **embedded public key**) |
520
+ | Address size | 20 bytes | 32 bytes |
521
+ | Recovery | ECDSA `ecrecover` | Extract embedded public key + PQC verify (no `ecrecover`) |
522
+ | `signTypedData` (EIP-712) | Supported | Not yet supported |
523
+
490
524
  ## Contracts
491
525
 
492
526
  ### `Contract`
@@ -679,6 +713,7 @@ const asOutput: Uint256 = 123n;
679
713
  - `sha512(data: BytesLike): string`
680
714
  - `ripemd160(data: BytesLike): string`
681
715
  - `id(text: string): string` (=`keccak256(utf8Bytes(text))`)
716
+ - `hashMessage(message: BytesLike): string` — EIP-191 personal-message digest, `keccak256("\x19Ethereum Signed Message:\n" + len + message)`. See [Message signing](#message-signing-eip-191).
682
717
  - `randomBytes(length: number): Uint8Array`
683
718
  - `computeHmac(algorithm: string, key: BytesLike, data: BytesLike): string`
684
719
  - `pbkdf2(password: BytesLike, salt: BytesLike, iterations: number, keylen: number, algorithm?: string): string`
package/README.md CHANGED
@@ -86,6 +86,52 @@ const restored = Wallet.fromEncryptedJsonSync(json, "mySecurePassword123");
86
86
  console.log(restored.address);
87
87
  ```
88
88
 
89
+ ## Message signing (EIP-191 / `personal_sign`)
90
+
91
+ Sign and verify arbitrary messages using QuantumCoin's post-quantum keys. The
92
+ message digest uses the exact same EIP-191 prefix as Ethereum
93
+ (`keccak256("\x19Ethereum Signed Message:\n" + len + message)`), so it is
94
+ byte-for-byte compatible with `personal_sign` in `quantum-coin-go`.
95
+
96
+ ```js
97
+ const { Wallet, verifyMessage, hashMessage } = require("quantumcoin");
98
+ const { Initialize } = require("quantumcoin/config");
99
+
100
+ await Initialize(null);
101
+
102
+ const wallet = Wallet.createRandom();
103
+
104
+ // async (ethers Signer contract) or sync
105
+ const signature = await wallet.signMessage("Hello Joe");
106
+ const signatureSync = wallet.signMessageSync("Hello Joe");
107
+
108
+ // verifyMessage is synchronous and returns the recovered signer address
109
+ const signer = verifyMessage("Hello Joe", signature);
110
+ console.log(signer === wallet.address); // true
111
+
112
+ // The EIP-191 digest is available on its own if needed
113
+ console.log(hashMessage("Hello Joe")); // 0x...32-byte hash
114
+ ```
115
+
116
+ ### Differences vs Ethereum
117
+
118
+ - **Signature shape:** not a 65-byte `(r, s, v)` value. It is an opaque,
119
+ scheme-dependent multi-kilobyte hex blob whose first byte is the scheme id and
120
+ which **embeds the signer's public key**. There is no `Signature`/`r`/`s`/`v`
121
+ object.
122
+ - **Address size:** recovered addresses are the full 32 bytes (66 hex chars).
123
+ - **No `ecrecover`:** `verifyMessage` does not recover a key from `(digest, sig)`
124
+ cryptographically. It extracts the embedded public key, verifies it against the
125
+ digest, and returns its address; a signature that fails verification throws.
126
+ - **Signing context:** `signMessage`/`signMessageSync` accept an optional
127
+ `signingContext`. When omitted it derives the compact context from the key type
128
+ (`0` for keyType 3, `1` for keyType 5); pass `2` to request the full-signature
129
+ scheme for a keyType 3 wallet.
130
+ - **Message size:** the message must be at most 1 MiB (once UTF-8 encoded);
131
+ larger inputs throw `INVALID_ARGUMENT`. The message is only ever hashed to a
132
+ 32-byte digest, so there is no need for larger payloads.
133
+ - **EIP-712 (`signTypedData`)** is not yet supported.
134
+
89
135
  ## Contracts (read-only)
90
136
 
91
137
  ```js
@@ -156,6 +202,7 @@ Common types:
156
202
  ```bash
157
203
  npm run example
158
204
  npm run example:wallet
205
+ npm run example:sign-message
159
206
  npm run example:contract:read
160
207
  npm run example:events
161
208
  # Run all examples (including SDK generator JS/TS)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quantumcoin",
3
- "version": "8.0.0",
3
+ "version": "8.0.1",
4
4
  "description": "QuantumCoin.js - a post quantum cryptography SDK for QuantumCoin",
5
5
  "main": "index.js",
6
6
  "types": "src/index.d.ts",
@@ -59,12 +59,14 @@
59
59
  "example:events:ts": "npx tsx examples/events.ts",
60
60
  "example:offline-signing": "node examples/offline-signing.js",
61
61
  "example:offline-signing:ts": "npx tsx examples/offline-signing.ts",
62
+ "example:sign-message": "node examples/sign-message.js",
63
+ "example:sign-message:ts": "npx tsx examples/sign-message.ts",
62
64
  "example:generator-js": "node examples/example-generator-sdk-js.js",
63
65
  "example:generator-js:ts": "npx tsx examples/example-generator-sdk-js.ts",
64
66
  "example:generator-ts": "node examples/example-generator-sdk-ts.js",
65
67
  "example:generator-ts:ts": "npx tsx examples/example-generator-sdk-ts.ts",
66
- "examples": "node examples/example.js && node examples/wallet-offline.js && node examples/read-operations.js && node examples/events.js && node examples/example-generator-sdk-js.js && node examples/example-generator-sdk-ts.js",
67
- "examples:ts": "npx tsx examples/example.ts && npx tsx examples/wallet-offline.ts && npx tsx examples/read-operations.ts && npx tsx examples/events.ts && npx tsx examples/example-generator-sdk-js.ts && npx tsx examples/example-generator-sdk-ts.ts",
68
+ "examples": "node examples/example.js && node examples/wallet-offline.js && node examples/sign-message.js && node examples/read-operations.js && node examples/events.js && node examples/example-generator-sdk-js.js && node examples/example-generator-sdk-ts.js",
69
+ "examples:ts": "npx tsx examples/example.ts && npx tsx examples/wallet-offline.ts && npx tsx examples/sign-message.ts && npx tsx examples/read-operations.ts && npx tsx examples/events.ts && npx tsx examples/example-generator-sdk-js.ts && npx tsx examples/example-generator-sdk-ts.ts",
68
70
  "build": "npx -p typescript tsc -p tsconfig.build.json && node scripts/copy-declarations.js"
69
71
  },
70
72
  "repository": {
package/src/index.d.ts CHANGED
@@ -29,6 +29,7 @@ declare const _exports: {
29
29
  sha512(data: string | Uint8Array): string;
30
30
  ripemd160(data: string | Uint8Array): string;
31
31
  id(text: string): string;
32
+ hashMessage(message: string | Uint8Array): string;
32
33
  randomBytes(length: number): Uint8Array;
33
34
  computeHmac(algorithm: string, key: string | Uint8Array, data: string | Uint8Array): string;
34
35
  pbkdf2(password: string | Uint8Array, salt: string | Uint8Array, iterations: number, keylen: number, algorithm?: string | undefined): string;
@@ -88,6 +89,7 @@ declare const _exports: {
88
89
  ContractTransactionResponse: typeof import("./contract/contract").ContractTransactionResponse;
89
90
  ContractTransactionReceipt: typeof import("./contract/contract").ContractTransactionReceipt;
90
91
  EventLog: typeof import("./contract/contract").EventLog;
92
+ verifyMessage(message: string | Uint8Array, signature: string | Uint8Array): string;
91
93
  SigningKey: typeof import("./wallet/wallet").SigningKey;
92
94
  AbstractSigner: typeof import("./wallet/wallet").AbstractSigner;
93
95
  BaseWallet: typeof import("./wallet/wallet").BaseWallet;
@@ -28,6 +28,24 @@ export function ripemd160(data: string | Uint8Array): string;
28
28
  * @returns {string}
29
29
  */
30
30
  export function id(text: string): string;
31
+ /**
32
+ * Compute the EIP-191 "personal message" digest for a message.
33
+ *
34
+ * QuantumCoin uses the exact same prefix as Ethereum, so this is byte-for-byte
35
+ * compatible with `personal_sign` in `quantum-coin-go`:
36
+ *
37
+ * keccak256("\x19Ethereum Signed Message:\n" + message.length + message)
38
+ *
39
+ * The resulting 32-byte hash is the digest passed to `Wallet.signMessage` /
40
+ * `Wallet.signMessageSync` and re-derived by `verifyMessage`. The decimal length
41
+ * prefix counts message BYTES, not characters. A string message is UTF-8
42
+ * encoded; a Uint8Array (or 0x hex string coerced via `arrayify`) is treated as
43
+ * raw bytes.
44
+ *
45
+ * @param {string|Uint8Array} message The message to hash. Strings are UTF-8 encoded.
46
+ * @returns {string} 0x-prefixed, 32-byte keccak256 digest.
47
+ */
48
+ export function hashMessage(message: string | Uint8Array): string;
31
49
  /**
32
50
  * Generate cryptographically strong random bytes.
33
51
  *
@@ -89,6 +89,32 @@ function id(text) {
89
89
  return keccak256(utf8ToBytes(text));
90
90
  }
91
91
 
92
+ /**
93
+ * Compute the EIP-191 "personal message" digest for a message.
94
+ *
95
+ * QuantumCoin uses the exact same prefix as Ethereum, so this is byte-for-byte
96
+ * compatible with `personal_sign` in `quantum-coin-go`:
97
+ *
98
+ * keccak256("\x19Ethereum Signed Message:\n" + message.length + message)
99
+ *
100
+ * The resulting 32-byte hash is the digest passed to `Wallet.signMessage` /
101
+ * `Wallet.signMessageSync` and re-derived by `verifyMessage`. The decimal length
102
+ * prefix counts message BYTES, not characters. A string message is UTF-8
103
+ * encoded; a Uint8Array (or 0x hex string coerced via `arrayify`) is treated as
104
+ * raw bytes.
105
+ *
106
+ * @param {string|Uint8Array} message The message to hash. Strings are UTF-8 encoded.
107
+ * @returns {string} 0x-prefixed, 32-byte keccak256 digest.
108
+ */
109
+ function hashMessage(message) {
110
+ const msgBytes = typeof message === "string" ? utf8ToBytes(message) : arrayify(message);
111
+ const prefix = utf8ToBytes(`\x19Ethereum Signed Message:\n${msgBytes.length}`);
112
+ const composed = new Uint8Array(prefix.length + msgBytes.length);
113
+ composed.set(prefix, 0);
114
+ composed.set(msgBytes, prefix.length);
115
+ return keccak256(composed);
116
+ }
117
+
92
118
  /**
93
119
  * Generate cryptographically strong random bytes.
94
120
  *
@@ -181,6 +207,7 @@ module.exports = {
181
207
  sha512,
182
208
  ripemd160,
183
209
  id,
210
+ hashMessage,
184
211
  randomBytes,
185
212
  computeHmac,
186
213
  pbkdf2,
@@ -17,6 +17,7 @@ declare const _exports: {
17
17
  sha512(data: string | Uint8Array): string;
18
18
  ripemd160(data: string | Uint8Array): string;
19
19
  id(text: string): string;
20
+ hashMessage(message: string | Uint8Array): string;
20
21
  randomBytes(length: number): Uint8Array;
21
22
  computeHmac(algorithm: string, key: string | Uint8Array, data: string | Uint8Array): string;
22
23
  pbkdf2(password: string | Uint8Array, salt: string | Uint8Array, iterations: number, keylen: number, algorithm?: string | undefined): string;
@@ -1,4 +1,5 @@
1
1
  declare const _exports: {
2
+ verifyMessage(message: string | Uint8Array, signature: string | Uint8Array): string;
2
3
  SigningKey: typeof import("./wallet").SigningKey;
3
4
  AbstractSigner: typeof import("./wallet").AbstractSigner;
4
5
  BaseWallet: typeof import("./wallet").BaseWallet;
@@ -51,6 +51,24 @@ export class BaseWallet extends AbstractSigner {
51
51
  * @returns {Promise<string>}
52
52
  */
53
53
  signTransaction(tx: import("../providers/provider").TransactionRequest): Promise<string>;
54
+ /**
55
+ * Sign an arbitrary message (EIP-191 personal-message scheme), synchronously.
56
+ * Returns an opaque post-quantum signature blob (0x hex) that embeds the
57
+ * signer's public key. Optional signingContext: when omitted/null the compact
58
+ * context is derived from the key type (0 for keyType 3, 1 for keyType 5);
59
+ * pass 2 for the full-signature scheme on a keyType 3 wallet.
60
+ * @param {string|Uint8Array} message
61
+ * @param {number|null=} signingContext
62
+ * @returns {string}
63
+ */
64
+ signMessageSync(message: string | Uint8Array, signingContext?: (number | null) | undefined): string;
65
+ /**
66
+ * Sign an arbitrary message (EIP-191). Async wrapper over signMessageSync.
67
+ * @param {string|Uint8Array} message
68
+ * @param {number|null=} signingContext
69
+ * @returns {Promise<string>}
70
+ */
71
+ signMessage(message: string | Uint8Array, signingContext?: (number | null) | undefined): Promise<string>;
54
72
  /**
55
73
  * Internal: sign a transaction and return both the raw serialized transaction
56
74
  * and the signer-computed transaction hash. The hash is later used to
@@ -225,3 +243,16 @@ export class VoidSigner extends AbstractSigner {
225
243
  _address: string;
226
244
  getAddress(): Promise<string>;
227
245
  }
246
+ /**
247
+ * Recover the signer's 32-byte address from an EIP-191 message signature.
248
+ *
249
+ * QuantumCoin has no ECDSA ecrecover; the post-quantum signature embeds the
250
+ * public key, which is extracted and verified against the message digest before
251
+ * the address is returned. Throws INVALID_ARGUMENT if the signature is malformed
252
+ * or does not verify. Synchronous, matching ethers' verifyMessage.
253
+ *
254
+ * @param {string|Uint8Array} message The original message (strings are UTF-8 encoded).
255
+ * @param {string|Uint8Array} signature The signature produced by signMessage.
256
+ * @returns {string} 0x-prefixed 32-byte signer address.
257
+ */
258
+ export function verifyMessage(message: string | Uint8Array, signature: string | Uint8Array): string;
@@ -12,8 +12,9 @@ const qcsdk = require("quantum-coin-js-sdk");
12
12
  const seedWords = require("seed-words");
13
13
  const { JsonRpcProvider } = require("../providers/json-rpc-provider");
14
14
  const { assertArgument, assertSecretArgument, makeError } = require("../errors");
15
- const { arrayify, bytesToHex, bytesToUtf8, hexToBytes, isHexString, normalizeHex } = require("../internal/hex");
16
- const { getAddress } = require("../utils/address");
15
+ const { arrayify, bytesToHex, bytesToUtf8, hexToBytes, isHexString, normalizeHex, utf8ToBytes } = require("../internal/hex");
16
+ const { computeAddress, getAddress } = require("../utils/address");
17
+ const { hashMessage } = require("../utils/hashing");
17
18
  const { WeiPerEther } = require("../constants");
18
19
 
19
20
  function _requireInitialized() {
@@ -34,6 +35,13 @@ function _bytesToNumberArray(bytes) {
34
35
  return Array.from(bytes);
35
36
  }
36
37
 
38
+ // Upper bound on the message size accepted by signMessage/signMessageSync. The
39
+ // message is only ever hashed to a 32-byte digest, so there is no protocol need
40
+ // for large inputs; this cap is a guardrail against accidentally signing an
41
+ // unbounded buffer (e.g. a whole file passed in by mistake). 1 MiB is far larger
42
+ // than any realistic personal_sign payload.
43
+ const MAX_MESSAGE_BYTES = 1024 * 1024;
44
+
37
45
  /**
38
46
  * Verify that a private/public key pair is internally consistent (the public
39
47
  * key really corresponds to the private key). Constructing a wallet from a
@@ -214,6 +222,74 @@ class BaseWallet extends AbstractSigner {
214
222
  return raw;
215
223
  }
216
224
 
225
+ /**
226
+ * Sign an arbitrary message using QuantumCoin's EIP-191 personal-message
227
+ * scheme, synchronously.
228
+ *
229
+ * The message is hashed with {@link hashMessage} (the same
230
+ * `keccak256("\x19Ethereum Signed Message:\n" + len + message)` digest used by
231
+ * `personal_sign` in quantum-coin-go), producing a 32-byte digest that is then
232
+ * signed with the wallet's post-quantum key.
233
+ *
234
+ * Unlike Ethereum's 65-byte `(r, s, v)` signature, the returned value is an
235
+ * opaque, scheme-dependent multi-kilobyte blob whose first byte is the scheme
236
+ * id and which EMBEDS the signer's public key. This is what allows
237
+ * {@link verifyMessage} to recover the signer address without an ecrecover
238
+ * primitive.
239
+ *
240
+ * @param {string|Uint8Array} message The message to sign (strings are UTF-8 encoded).
241
+ * Must be at most {@link MAX_MESSAGE_BYTES} (1 MiB) once UTF-8 encoded.
242
+ * @param {number|null=} signingContext Optional signing context. When omitted or
243
+ * null, `quantum-coin-js-sdk` derives the compact context from the key type
244
+ * (0 for keyType 3, 1 for keyType 5); pass 2 to request the full-signature
245
+ * scheme for a keyType 3 wallet.
246
+ * @returns {string} 0x-prefixed signature blob (public key embedded).
247
+ * @throws {TypeError} INVALID_ARGUMENT if the message exceeds the size limit.
248
+ */
249
+ signMessageSync(message, signingContext) {
250
+ _requireInitialized();
251
+ const msgBytes = typeof message === "string" ? utf8ToBytes(message) : arrayify(message);
252
+ assertArgument(
253
+ msgBytes.length <= MAX_MESSAGE_BYTES,
254
+ `message too long (max ${MAX_MESSAGE_BYTES} bytes)`,
255
+ "message",
256
+ msgBytes.length,
257
+ );
258
+ const digestArr = _bytesToNumberArray(hexToBytes(hashMessage(msgBytes)));
259
+ const privArr = _bytesToNumberArray(this.signingKey.privateKeyBytes);
260
+ const pubArr = _bytesToNumberArray(this.signingKey.publicKeyBytes);
261
+ const ctx = signingContext == null ? null : signingContext;
262
+ const res = qcsdk.sign(privArr, digestArr, ctx);
263
+ if (!res || typeof res !== "object" || res.resultCode !== 0 || res.signature == null) {
264
+ throw makeError("signMessage failed", "UNKNOWN_ERROR", {
265
+ resultCode: res && typeof res === "object" ? res.resultCode : null,
266
+ });
267
+ }
268
+ // qcsdk.sign returns the signature-only bytes. Combine the public key into
269
+ // the blob (sig + pubKey) so the result is self-describing and verifyMessage
270
+ // can recover the signer without a separately supplied public key.
271
+ const sigArr = Array.from(res.signature);
272
+ const combined = qcsdk.combinePublicKeySignature(pubArr, sigArr);
273
+ if (typeof combined !== "string") {
274
+ throw makeError("signMessage failed to combine public key with signature", "UNKNOWN_ERROR", {});
275
+ }
276
+ return normalizeHex(combined);
277
+ }
278
+
279
+ /**
280
+ * Sign an arbitrary message (EIP-191). Async wrapper over
281
+ * {@link BaseWallet#signMessageSync} that honors the ethers `Signer` interface
282
+ * contract (`Signer.signMessage` is async); the underlying PQC signing is
283
+ * synchronous.
284
+ *
285
+ * @param {string|Uint8Array} message The message to sign (strings are UTF-8 encoded).
286
+ * @param {number|null=} signingContext Optional signing context (see signMessageSync).
287
+ * @returns {Promise<string>} 0x-prefixed signature blob (public key embedded).
288
+ */
289
+ async signMessage(message, signingContext) {
290
+ return this.signMessageSync(message, signingContext);
291
+ }
292
+
217
293
  /**
218
294
  * Internal: sign a transaction and return both the raw serialized transaction
219
295
  * and the signer-computed transaction hash. The hash is later used to
@@ -739,6 +815,39 @@ class VoidSigner extends AbstractSigner {
739
815
  }
740
816
  }
741
817
 
818
+ /**
819
+ * Recover the signer's 32-byte address from an EIP-191 message signature.
820
+ *
821
+ * QuantumCoin has no ECDSA `ecrecover`: signatures are opaque post-quantum blobs
822
+ * that embed the signer's public key. This function re-derives the EIP-191
823
+ * digest with {@link hashMessage}, extracts and cryptographically verifies the
824
+ * embedded public key against that digest via
825
+ * `quantum-coin-js-sdk.publicKeyFromSignature`, and returns the corresponding
826
+ * 32-byte address. If the signature does not verify there is nothing to recover,
827
+ * so the function throws.
828
+ *
829
+ * Synchronous, matching ethers' `verifyMessage` (there is no async variant).
830
+ *
831
+ * @param {string|Uint8Array} message The original message (strings are UTF-8 encoded).
832
+ * @param {string|Uint8Array} signature The signature produced by `signMessage`.
833
+ * @returns {string} 0x-prefixed 32-byte signer address.
834
+ * @throws {TypeError} INVALID_ARGUMENT if the signature is malformed or does not verify.
835
+ */
836
+ function verifyMessage(message, signature) {
837
+ _requireInitialized();
838
+ const digestHex = hashMessage(message);
839
+ const digestArr = _bytesToNumberArray(hexToBytes(digestHex));
840
+ const sigArr = _bytesToNumberArray(arrayify(signature));
841
+ const pubHex = qcsdk.publicKeyFromSignature(digestArr, sigArr);
842
+ if (typeof pubHex !== "string") {
843
+ throw makeError("verifyMessage failed: signature did not verify", "INVALID_ARGUMENT", {
844
+ argument: "signature",
845
+ value: "[signature]",
846
+ });
847
+ }
848
+ return computeAddress(pubHex);
849
+ }
850
+
742
851
  module.exports = {
743
852
  SigningKey,
744
853
  AbstractSigner,
@@ -747,5 +856,6 @@ module.exports = {
747
856
  NonceManager,
748
857
  JsonRpcSigner,
749
858
  VoidSigner,
859
+ verifyMessage,
750
860
  };
751
861