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 +35 -0
- package/README.md +47 -0
- package/package.json +5 -3
- package/src/index.d.ts +2 -0
- package/src/utils/hashing.d.ts +18 -0
- package/src/utils/hashing.js +27 -0
- package/src/utils/index.d.ts +1 -0
- package/src/wallet/index.d.ts +1 -0
- package/src/wallet/wallet.d.ts +31 -0
- package/src/wallet/wallet.js +112 -2
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.
|
|
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;
|
package/src/utils/hashing.d.ts
CHANGED
|
@@ -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
|
*
|
package/src/utils/hashing.js
CHANGED
|
@@ -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,
|
package/src/utils/index.d.ts
CHANGED
|
@@ -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;
|
package/src/wallet/index.d.ts
CHANGED
|
@@ -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;
|
package/src/wallet/wallet.d.ts
CHANGED
|
@@ -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;
|
package/src/wallet/wallet.js
CHANGED
|
@@ -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
|
|