@qntx/siwx 0.0.0-stage → 0.7.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.
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +38 -2
- package/dist/eip4361-XA-TUOKF.mjs +125 -0
- package/dist/evm.d.mts +41 -0
- package/dist/evm.mjs +289 -0
- package/dist/index.d.mts +113 -0
- package/dist/index.mjs +32 -0
- package/dist/message-ZewmOzps.mjs +510 -0
- package/dist/namespace-Cl7PJYC-.mjs +68 -0
- package/dist/namespace-DlgXqLsI.d.mts +105 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/svm.d.mts +42 -0
- package/dist/svm.mjs +329 -0
- package/package.json +84 -4
package/dist/evm.mjs
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { g as createAccountId, i as utf8Encode, v as SiwxError, y as fieldError } from "./message-ZewmOzps.mjs";
|
|
2
|
+
import { n as format, r as parseLabeled } from "./eip4361-XA-TUOKF.mjs";
|
|
3
|
+
import { hexToBytes } from "@noble/hashes/utils.js";
|
|
4
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
5
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
6
|
+
import { bytesToHex, bytesToNumberBE, equalBytes, hexToBytes as hexToBytes$1, numberToBytesBE } from "@noble/curves/utils.js";
|
|
7
|
+
//#region src/evm/address.ts
|
|
8
|
+
//! EIP-55 / EVM address + chain-id rules, matching `siwx::evm`.
|
|
9
|
+
const HEX40 = /^[0-9a-fA-F]{40}$/;
|
|
10
|
+
/** EIP-55 checksum spelling of a `0x`-prefixed 40-hex address. */
|
|
11
|
+
function toChecksumAddress(address) {
|
|
12
|
+
if (!address.startsWith("0x")) throw fieldError("address", "expected 0x + 40 hex");
|
|
13
|
+
const body = address.slice(2);
|
|
14
|
+
if (!HEX40.test(body)) throw fieldError("address", "expected 0x + 40 hex");
|
|
15
|
+
const lower = body.toLowerCase();
|
|
16
|
+
const hash = keccak_256(utf8Encode(lower));
|
|
17
|
+
let out = "0x";
|
|
18
|
+
for (let i = 0; i < 40; i++) {
|
|
19
|
+
const c = lower.charAt(i);
|
|
20
|
+
const nibble = (hash[Math.floor(i / 2)] ?? 0) >> (i % 2 === 0 ? 4 : 0) & 15;
|
|
21
|
+
out += /[a-f]/.test(c) && nibble >= 8 ? c.toUpperCase() : c;
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Validate `0x` + 40 hex: all-lowercase/all-uppercase or valid EIP-55.
|
|
27
|
+
*
|
|
28
|
+
* @throws {SiwxError} `InvalidField` `address`.
|
|
29
|
+
*/
|
|
30
|
+
function validateAddress(address) {
|
|
31
|
+
parseEip55(address);
|
|
32
|
+
}
|
|
33
|
+
const badAddress = () => fieldError("address", "expected 0x + 40 hex");
|
|
34
|
+
/** Parse an EVM address to its 20 bytes under the canonical rule. */
|
|
35
|
+
function parseEip55(address) {
|
|
36
|
+
const bad = badAddress;
|
|
37
|
+
if (!address.startsWith("0x")) throw bad();
|
|
38
|
+
const hex = address.slice(2);
|
|
39
|
+
if (!HEX40.test(hex)) throw bad();
|
|
40
|
+
const hasLower = /[a-f]/.test(hex);
|
|
41
|
+
const hasUpper = /[A-F]/.test(hex);
|
|
42
|
+
if (hasLower && hasUpper && address !== toChecksumAddress(address)) throw fieldError("address", "bad EIP-55 checksum");
|
|
43
|
+
return hexToBytes(hex);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* EVM `chain-id`: `[0-9]+`, no leading zero unless `"0"`, fits in `u64`.
|
|
47
|
+
*
|
|
48
|
+
* @throws {SiwxError} `InvalidField` `chainId`.
|
|
49
|
+
*/
|
|
50
|
+
function parseEvmChainId(chainId) {
|
|
51
|
+
if (chainId.length === 0) throw fieldError("chainId", "empty");
|
|
52
|
+
if (!/^[0-9]+$/.test(chainId)) throw fieldError("chainId", "not decimal");
|
|
53
|
+
if (chainId.length > 1 && chainId.startsWith("0")) throw fieldError("chainId", "leading zero");
|
|
54
|
+
const value = BigInt(chainId);
|
|
55
|
+
if (value > 18446744073709551615n) throw fieldError("chainId", "overflow");
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/evm/eip191.ts
|
|
60
|
+
//! EIP-191 `personal_sign` verification.
|
|
61
|
+
//!
|
|
62
|
+
//! Decode order (cross-language contract, mirrored with Rust): length ≠
|
|
63
|
+
//! 65 → `v ∉ {0,1,27,28}` → `r`/`s` equal to 0 or ≥ n → `s > n/2` (all
|
|
64
|
+
//! `InvalidSignature`) → recovery failure or address mismatch →
|
|
65
|
+
//! `VerificationFailed`.
|
|
66
|
+
const N = secp256k1.Point.Fn.ORDER;
|
|
67
|
+
const N_HALF = N / 2n;
|
|
68
|
+
/** `keccak256("\x19Ethereum Signed Message:\n" + len + message)`. */
|
|
69
|
+
function eip191Hash(message) {
|
|
70
|
+
const prefix = utf8Encode(`\u0019Ethereum Signed Message:\n${message.length}`);
|
|
71
|
+
const joined = new Uint8Array(prefix.length + message.length);
|
|
72
|
+
joined.set(prefix);
|
|
73
|
+
joined.set(message, prefix.length);
|
|
74
|
+
return keccak_256(joined);
|
|
75
|
+
}
|
|
76
|
+
/** Verify a 65-byte `r‖s‖v` EIP-191 signature over `signed`. */
|
|
77
|
+
function verifyEip191(message, signed, signature) {
|
|
78
|
+
if (signature.length !== 65) throw new SiwxError({
|
|
79
|
+
code: "InvalidSignature",
|
|
80
|
+
reason: "EIP-191 signature must be 65 bytes"
|
|
81
|
+
});
|
|
82
|
+
const v = signature[64] ?? 0;
|
|
83
|
+
if (v !== 0 && v !== 1 && v !== 27 && v !== 28) throw new SiwxError({
|
|
84
|
+
code: "InvalidSignature",
|
|
85
|
+
reason: "v must be 0, 1, 27 or 28"
|
|
86
|
+
});
|
|
87
|
+
const r = bytesToNumberBE(signature.subarray(0, 32));
|
|
88
|
+
const s = bytesToNumberBE(signature.subarray(32, 64));
|
|
89
|
+
if (r === 0n || r >= N || s === 0n || s >= N) throw new SiwxError({
|
|
90
|
+
code: "InvalidSignature",
|
|
91
|
+
reason: "r/s out of range"
|
|
92
|
+
});
|
|
93
|
+
if (s > N_HALF) throw new SiwxError({
|
|
94
|
+
code: "InvalidSignature",
|
|
95
|
+
reason: "high-s (EIP-2)"
|
|
96
|
+
});
|
|
97
|
+
const hash = eip191Hash(signed);
|
|
98
|
+
let recovered;
|
|
99
|
+
try {
|
|
100
|
+
const uncompressed = secp256k1.Signature.fromBytes(signature.subarray(0, 64), "compact").addRecoveryBit(v % 27).recoverPublicKey(hash).toBytes(false);
|
|
101
|
+
recovered = keccak_256(uncompressed.subarray(1)).subarray(12);
|
|
102
|
+
} catch {
|
|
103
|
+
throw new SiwxError({ code: "VerificationFailed" });
|
|
104
|
+
}
|
|
105
|
+
const expected = parseEip55(message.address);
|
|
106
|
+
if (!equalBytes(recovered, expected)) throw new SiwxError({ code: "VerificationFailed" });
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/evm/validator.ts
|
|
110
|
+
//! On-chain smart-account verification: the ERC-6492 universal validator via a
|
|
111
|
+
//! deployless `eth_call`. Covers deployed ERC-1271 accounts, counterfactual
|
|
112
|
+
//! ERC-6492 accounts and EOAs. Providers are injected; the library builds no
|
|
113
|
+
//! transports and applies no timeouts — transport policy belongs to the
|
|
114
|
+
//! provider owner.
|
|
115
|
+
const BYTECODE = hexToBytes$1("608060405234801561001057600080fd5b5060405161069438038061069483398101604081905261002f9161051e565b600061003c848484610048565b9050806000526001601ff35b60007f64926492649264926492649264926492649264926492649264926492649264926100748361040c565b036101e7576000606080848060200190518101906100929190610577565b60405192955090935091506000906001600160a01b038516906100b69085906105dd565b6000604051808303816000865af19150503d80600081146100f3576040519150601f19603f3d011682016040523d82523d6000602084013e6100f8565b606091505b50509050876001600160a01b03163b60000361016057806101605760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90610190908b9087906004016105f9565b602060405180830381865afa1580156101ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d19190610633565b6001600160e01b03191614945050505050610405565b6001600160a01b0384163b1561027a57604051630b135d3f60e11b808252906001600160a01b03861690631626ba7e9061022790879087906004016105f9565b602060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102689190610633565b6001600160e01b031916149050610405565b81516041146102df5760405162461bcd60e51b815260206004820152603a602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610157565b6102e7610425565b5060208201516040808401518451859392600091859190811061030c5761030c61065d565b016020015160f81c9050601b811480159061032b57508060ff16601c14155b1561038c5760405162461bcd60e51b815260206004820152603b602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c756500000000006064820152608401610157565b60408051600081526020810180835289905260ff83169181019190915260608101849052608081018390526001600160a01b0389169060019060a0016020604051602081039080840390855afa1580156103ea573d6000803e3d6000fd5b505050602060405103516001600160a01b0316149450505050505b9392505050565b600060208251101561041d57600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b038116811461045857600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561048c578181015183820152602001610474565b50506000910152565b600082601f8301126104a657600080fd5b81516001600160401b038111156104bf576104bf61045b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156104ed576104ed61045b565b60405281815283820160200185101561050557600080fd5b610516826020830160208701610471565b949350505050565b60008060006060848603121561053357600080fd5b835161053e81610443565b6020850151604086015191945092506001600160401b0381111561056157600080fd5b61056d86828701610495565b9150509250925092565b60008060006060848603121561058c57600080fd5b835161059781610443565b60208501519093506001600160401b038111156105b357600080fd5b6105bf86828701610495565b604086015190935090506001600160401b0381111561056157600080fd5b600082516105ef818460208701610471565b9190910192915050565b828152604060208201526000825180604084015261061e816060850160208701610471565b601f01601f1916919091016060019392505050565b60006020828403121561064557600080fd5b81516001600160e01b03198116811461040557600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572");
|
|
116
|
+
/** Abi.encode(address signer, bytes32 hash, bytes signature). */
|
|
117
|
+
function deploylessCalldata(signer, hash, signature) {
|
|
118
|
+
const addr = hexToBytes$1(signer.slice(2));
|
|
119
|
+
const word = 32;
|
|
120
|
+
const out = new Uint8Array(BYTECODE.length + 96 + word + Math.ceil(signature.length / word) * word);
|
|
121
|
+
const bc = BYTECODE;
|
|
122
|
+
out.set(bc, 0);
|
|
123
|
+
let o = bc.length;
|
|
124
|
+
out.set(addr, o + (word - addr.length));
|
|
125
|
+
o += word;
|
|
126
|
+
out.set(hash, o);
|
|
127
|
+
o += word;
|
|
128
|
+
out.set(numberToBytesBE(BigInt(96), word), o);
|
|
129
|
+
o += word;
|
|
130
|
+
out.set(numberToBytesBE(BigInt(signature.length), word), o);
|
|
131
|
+
out.set(signature, o + word);
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
const malformed = () => new SiwxError({
|
|
135
|
+
code: "Backend",
|
|
136
|
+
reason: "malformed eth_call result"
|
|
137
|
+
});
|
|
138
|
+
/**
|
|
139
|
+
* Interpret a deployless validator `eth_call` result: `0x01` or a 32-byte word of zeros ending `01`
|
|
140
|
+
* is valid; the same shapes ending `00` are `false`; anything else is malformed.
|
|
141
|
+
*/
|
|
142
|
+
function decodeResult(hex) {
|
|
143
|
+
const data = hex === "" || hex === "0x" ? /* @__PURE__ */ new Uint8Array(0) : hexToBytes$1(hex.slice(2));
|
|
144
|
+
if (data.length === 1) {
|
|
145
|
+
if (data[0] === 1) return true;
|
|
146
|
+
if (data[0] === 0) return false;
|
|
147
|
+
throw malformed();
|
|
148
|
+
}
|
|
149
|
+
if (data.length === 32) {
|
|
150
|
+
for (let i = 0; i < 31; i++) if (data[i] !== 0) throw malformed();
|
|
151
|
+
if (data[31] === 1) return true;
|
|
152
|
+
if (data[31] === 0) return false;
|
|
153
|
+
}
|
|
154
|
+
throw malformed();
|
|
155
|
+
}
|
|
156
|
+
/** JSON-RPC code 3 (EIP-1474 execution reverted) ⇒ signature rejected. */
|
|
157
|
+
const codeOf = (error) => typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
158
|
+
function isRevert(error) {
|
|
159
|
+
const code = codeOf(error);
|
|
160
|
+
return typeof code === "number" && code === 3;
|
|
161
|
+
}
|
|
162
|
+
function backend() {
|
|
163
|
+
return new SiwxError({
|
|
164
|
+
code: "Backend",
|
|
165
|
+
reason: "RPC request failed"
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const MAGIC = (() => {
|
|
169
|
+
const m = /* @__PURE__ */ new Uint8Array(32);
|
|
170
|
+
for (let i = 0; i < m.length; i++) m[i] = i % 2 === 0 ? 100 : 146;
|
|
171
|
+
return m;
|
|
172
|
+
})();
|
|
173
|
+
/** True when `signature` ends with the ERC-6492 detection suffix. */
|
|
174
|
+
function has6492MagicSuffix(signature) {
|
|
175
|
+
if (signature.length < MAGIC.length) return false;
|
|
176
|
+
for (let i = 0; i < MAGIC.length; i++) if (signature[signature.length - MAGIC.length + i] !== MAGIC[i]) return false;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
/** `eth_chainId` as a decimal number; malformed → `Backend`. */
|
|
180
|
+
async function providerChainId(provider) {
|
|
181
|
+
let result;
|
|
182
|
+
try {
|
|
183
|
+
result = await provider.request({ method: "eth_chainId" });
|
|
184
|
+
} catch {
|
|
185
|
+
throw backend();
|
|
186
|
+
}
|
|
187
|
+
if (typeof result !== "string" || !/^0x[0-9a-fA-F]+$/.test(result)) throw backend();
|
|
188
|
+
return BigInt(result);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Deployless `eth_call` against the vendored universal validator. `provider`'s chain id must
|
|
192
|
+
* already equal the message chain id (the caller caches the binding).
|
|
193
|
+
*/
|
|
194
|
+
async function verifyOnChain(provider, message, signed, signature) {
|
|
195
|
+
parseEip55(message.address);
|
|
196
|
+
const hash = eip191Hash(signed);
|
|
197
|
+
const data = `0x${bytesToHex(deploylessCalldata(message.address, hash, signature))}`;
|
|
198
|
+
let result;
|
|
199
|
+
try {
|
|
200
|
+
result = await provider.request({
|
|
201
|
+
method: "eth_call",
|
|
202
|
+
params: [{
|
|
203
|
+
data,
|
|
204
|
+
input: data
|
|
205
|
+
}, "latest"]
|
|
206
|
+
});
|
|
207
|
+
} catch (error) {
|
|
208
|
+
throw isRevert(error) ? new SiwxError({ code: "VerificationFailed" }) : backend();
|
|
209
|
+
}
|
|
210
|
+
if (typeof result !== "string" || !/^0x[0-9a-fA-F]*$/.test(result)) throw backend();
|
|
211
|
+
if (!decodeResult(result)) throw new SiwxError({ code: "VerificationFailed" });
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/evm/index.ts
|
|
215
|
+
//! `@qntx/siwx/evm` — EIP-155 (`eip155`) namespace profile.
|
|
216
|
+
//!
|
|
217
|
+
//! `account()` emits the canonical EIP-55 checksum spelling, so
|
|
218
|
+
//! lowercase / uppercase / checksummed spellings of the same address map
|
|
219
|
+
//! to the same `AccountId`.
|
|
220
|
+
const CHAIN_NAME = "Ethereum";
|
|
221
|
+
const NAMESPACE = "eip155";
|
|
222
|
+
function evmAccount(message) {
|
|
223
|
+
parseEip55(message.address);
|
|
224
|
+
parseEvmChainId(message.chainId);
|
|
225
|
+
return createAccountId(NAMESPACE, message.chainId, toChecksumAddress(message.address));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Ethereum (`eip155`) namespace. EIP-191 runs fully offline; a signature carrying the ERC-6492
|
|
229
|
+
* magic suffix, or an EIP-191 failure on a chain with a configured provider, takes the on-chain
|
|
230
|
+
* universal-validator path. Providers own all transport policy — the library applies no timeouts —
|
|
231
|
+
* and `eth_chainId` is bound to the message chain id (cached per provider on success only) before
|
|
232
|
+
* any `eth_call`.
|
|
233
|
+
*/
|
|
234
|
+
function evm(options) {
|
|
235
|
+
const rpc = options?.rpc ?? {};
|
|
236
|
+
const verifiedChains = /* @__PURE__ */ new Set();
|
|
237
|
+
const providerFor = (chainId) => {
|
|
238
|
+
const key = parseEvmChainId(chainId).toString(10);
|
|
239
|
+
return rpc[key];
|
|
240
|
+
};
|
|
241
|
+
const onChain = async (message, signed, signature, eip191Err) => {
|
|
242
|
+
const key = parseEvmChainId(message.chainId).toString(10);
|
|
243
|
+
const provider = providerFor(message.chainId);
|
|
244
|
+
if (provider === void 0) throw eip191Err ?? new SiwxError({
|
|
245
|
+
code: "Unsupported",
|
|
246
|
+
reason: "EIP-6492 requires RPC"
|
|
247
|
+
});
|
|
248
|
+
if (!verifiedChains.has(key)) {
|
|
249
|
+
const rpcChain = await providerChainId(provider);
|
|
250
|
+
if (rpcChain !== parseEvmChainId(message.chainId)) throw new SiwxError({
|
|
251
|
+
code: "Mismatch",
|
|
252
|
+
field: "chainId",
|
|
253
|
+
expected: key,
|
|
254
|
+
actual: rpcChain.toString()
|
|
255
|
+
});
|
|
256
|
+
verifiedChains.add(key);
|
|
257
|
+
}
|
|
258
|
+
await verifyOnChain(provider, message, signed, signature);
|
|
259
|
+
};
|
|
260
|
+
return {
|
|
261
|
+
namespace: NAMESPACE,
|
|
262
|
+
label: CHAIN_NAME,
|
|
263
|
+
account: evmAccount,
|
|
264
|
+
format(message) {
|
|
265
|
+
evmAccount(message);
|
|
266
|
+
return format(CHAIN_NAME, message);
|
|
267
|
+
},
|
|
268
|
+
parse(signed) {
|
|
269
|
+
return parseLabeled(CHAIN_NAME, signed);
|
|
270
|
+
},
|
|
271
|
+
verify: async (message, signed, signature) => {
|
|
272
|
+
if (has6492MagicSuffix(signature)) {
|
|
273
|
+
await onChain(message, signed, signature, void 0);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
verifyEip191(message, signed, signature);
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (error instanceof SiwxError && providerFor(message.chainId) !== void 0) {
|
|
280
|
+
await onChain(message, signed, signature, error);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
export { evm, parseEvmChainId, toChecksumAddress, validateAddress };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { a as checkExpect, c as createMessage, d as parseAccountId, i as Expect, l as AccountId, n as Namespace, o as Message, r as authenticate, s as MessageFields, t as Authenticated, u as createAccountId } from "./namespace-DlgXqLsI.mjs";
|
|
2
|
+
declare namespace eip4361_d_exports {
|
|
3
|
+
export { Parsed, format, parse, parseBytes, parseLabeled };
|
|
4
|
+
}
|
|
5
|
+
/** Result of [`parse`]: preamble label plus the validated message. */
|
|
6
|
+
type Parsed = {
|
|
7
|
+
readonly label: string;
|
|
8
|
+
readonly message: Message;
|
|
9
|
+
};
|
|
10
|
+
/** Render `message` as an ERC-4361 signing text under `label`. */
|
|
11
|
+
declare function format(label: string, message: Message): string;
|
|
12
|
+
/** Parse `text` into a label + validated message. */
|
|
13
|
+
declare function parse(text: string): Parsed;
|
|
14
|
+
/** [`parseBytes`]: UTF-8 strictness then [`parse`]. */
|
|
15
|
+
declare function parseBytes(bytes: Uint8Array): Parsed;
|
|
16
|
+
/** [`parseBytes`] plus a required preamble `label`. */
|
|
17
|
+
declare function parseLabeled(label: string, signed: Uint8Array): Message;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/core/error.d.ts
|
|
20
|
+
/** CAIP-122 message field or preamble component, camelCase-named. */
|
|
21
|
+
type Field = "label" | "scheme" | "domain" | "address" | "statement" | "uri" | "version" | "chainId" | "nonce" | "issuedAt" | "expirationTime" | "notBefore" | "requestId" | "resources";
|
|
22
|
+
/** Layout-level (ABNF) syntax failure kinds. */
|
|
23
|
+
type SyntaxKind = "InvalidUtf8" | "CarriageReturn" | "MissingPreamble" | "MissingAccountSuffix" | "UnexpectedEof" | "ExpectedBlankLine" | "MissingField" | "UnexpectedTrailing" | "ResourceSyntax" | "InvalidEnvelope";
|
|
24
|
+
/** Per-code error detail; `switch (info.code)` narrows the payload. */
|
|
25
|
+
type SiwxErrorInfo = Readonly<{
|
|
26
|
+
code: "MessageTooLarge";
|
|
27
|
+
length: number;
|
|
28
|
+
max: number;
|
|
29
|
+
}> | Readonly<{
|
|
30
|
+
code: "Syntax";
|
|
31
|
+
syntax: "MissingField";
|
|
32
|
+
field: Field;
|
|
33
|
+
}> | Readonly<{
|
|
34
|
+
code: "Syntax";
|
|
35
|
+
syntax: Exclude<SyntaxKind, "MissingField">;
|
|
36
|
+
}> | Readonly<{
|
|
37
|
+
code: "InvalidField";
|
|
38
|
+
field: Field;
|
|
39
|
+
reason: string;
|
|
40
|
+
}> | Readonly<{
|
|
41
|
+
code: "Mismatch";
|
|
42
|
+
field: Field;
|
|
43
|
+
expected: string;
|
|
44
|
+
actual: string | undefined;
|
|
45
|
+
}> | Readonly<{
|
|
46
|
+
code: "Expired";
|
|
47
|
+
}> | Readonly<{
|
|
48
|
+
code: "NotYetValid";
|
|
49
|
+
}> | Readonly<{
|
|
50
|
+
code: "IssuedAtTooOld";
|
|
51
|
+
}> | Readonly<{
|
|
52
|
+
code: "InvalidSignature";
|
|
53
|
+
reason: string;
|
|
54
|
+
}> | Readonly<{
|
|
55
|
+
code: "VerificationFailed";
|
|
56
|
+
}> | Readonly<{
|
|
57
|
+
code: "Unsupported";
|
|
58
|
+
reason: string;
|
|
59
|
+
}> | Readonly<{
|
|
60
|
+
code: "Backend";
|
|
61
|
+
reason: string;
|
|
62
|
+
}>;
|
|
63
|
+
/** Stable error code set shared with the Rust implementation. */
|
|
64
|
+
type SiwxErrorCode = SiwxErrorInfo["code"];
|
|
65
|
+
/** The single error type thrown by every library failure. */
|
|
66
|
+
export declare class SiwxError extends Error {
|
|
67
|
+
/** Structured payload; `code` matches the Rust `Error::code()`. */
|
|
68
|
+
readonly info: SiwxErrorInfo;
|
|
69
|
+
constructor(info: SiwxErrorInfo);
|
|
70
|
+
/** Stable variant name for cross-language contract tests. */
|
|
71
|
+
get code(): SiwxErrorCode;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/core/limits.d.ts
|
|
75
|
+
/** Minimum EIP-4361 nonce length. */
|
|
76
|
+
export declare const MIN_NONCE_LEN = 8;
|
|
77
|
+
/** Maximum accepted signing-text size in bytes. */
|
|
78
|
+
export declare const MAX_MESSAGE_BYTES = 16384;
|
|
79
|
+
/** Maximum number of entries in the `Resources` list. */
|
|
80
|
+
export declare const MAX_RESOURCES = 32;
|
|
81
|
+
/** Maximum accepted `statement` size in bytes. */
|
|
82
|
+
export declare const MAX_STATEMENT_BYTES = 4096;
|
|
83
|
+
/** Maximum accepted `request_id` size in bytes. */
|
|
84
|
+
export declare const MAX_REQUEST_ID_BYTES = 128;
|
|
85
|
+
/** Maximum accepted URI size in bytes (`uri` and each resource). */
|
|
86
|
+
export declare const MAX_URI_BYTES = 2048;
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/core/nonce.d.ts
|
|
89
|
+
/** Generate a random nonce: 17 unbiased ASCII alphanumerics (≥ 8 required). */
|
|
90
|
+
export declare function generateNonce(): string;
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/core/timestamp.d.ts
|
|
93
|
+
/** Parsed RFC 3339 timestamp: original lexeme + epoch milliseconds. */
|
|
94
|
+
type Timestamp = {
|
|
95
|
+
/** Original RFC 3339 lexical form (formatter input). */
|
|
96
|
+
readonly lexeme: string;
|
|
97
|
+
/** Instant in whole milliseconds since the Unix epoch. */
|
|
98
|
+
readonly epochMs: number;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Parse an RFC 3339 `date-time`; `undefined` when invalid.
|
|
102
|
+
*
|
|
103
|
+
* Requires `T`/`t` and `Z`/`z`/`±HH:MM`; optional fraction; real calendar validity (Feb 30
|
|
104
|
+
* rejected); leap second `:60` rejected like Rust `time`.
|
|
105
|
+
*/
|
|
106
|
+
export declare function parseTimestamp(s: string): Timestamp | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* `Date.prototype.toISOString()` semantics: UTC, millisecond precision, `YYYY-MM-DDTHH:MM:SS.sssZ`;
|
|
109
|
+
* `undefined` for invalid dates and years outside `0000..=9999`.
|
|
110
|
+
*/
|
|
111
|
+
export declare function timestampFromDate(date: Date): string | undefined;
|
|
112
|
+
//#endregion
|
|
113
|
+
export { type AccountId, type Authenticated, type Expect, type Field, type Message, type MessageFields, type Namespace, type Parsed, type SiwxErrorCode, type SiwxErrorInfo, type SyntaxKind, type Timestamp, authenticate, checkExpect, createAccountId, createMessage, eip4361_d_exports as eip4361, parseAccountId };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { _ as parseAccountId, a as parseTimestamp, c as MAX_REQUEST_ID_BYTES, d as MAX_URI_BYTES, f as MIN_NONCE_LEN, g as createAccountId, l as MAX_RESOURCES, o as timestampFromDate, s as MAX_MESSAGE_BYTES, t as createMessage, u as MAX_STATEMENT_BYTES, v as SiwxError } from "./message-ZewmOzps.mjs";
|
|
2
|
+
import { t as eip4361_exports } from "./eip4361-XA-TUOKF.mjs";
|
|
3
|
+
import { r as checkExpect, t as authenticate } from "./namespace-Cl7PJYC-.mjs";
|
|
4
|
+
import { randomBytes } from "@noble/hashes/utils.js";
|
|
5
|
+
//#region src/core/nonce.ts
|
|
6
|
+
//! Cryptographically secure nonce generation (17 unbiased ASCII
|
|
7
|
+
//! alphanumerics via rejection sampling over `randomBytes`).
|
|
8
|
+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
9
|
+
const LEN = 17;
|
|
10
|
+
/** Generate a random nonce: 17 unbiased ASCII alphanumerics (≥ 8 required). */
|
|
11
|
+
function generateNonce() {
|
|
12
|
+
let out = "";
|
|
13
|
+
while (out.length < LEN) {
|
|
14
|
+
const bytes = randomBytes(34);
|
|
15
|
+
for (const b of bytes) if (b < 248) {
|
|
16
|
+
out += ALPHABET[b % 62];
|
|
17
|
+
if (out.length === LEN) break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/index.ts
|
|
24
|
+
//! `@qntx/siwx` — Sign-In with X (CAIP-122) for TypeScript.
|
|
25
|
+
//!
|
|
26
|
+
//! Mirrors the `siwx` Rust crate 1:1: same error codes, field names,
|
|
27
|
+
//! validation order, layout, and `Expect` semantics; both languages run
|
|
28
|
+
//! the same vector files in `vectors/`.
|
|
29
|
+
//!
|
|
30
|
+
//! The Ethereum namespace lives at `@qntx/siwx/evm`.
|
|
31
|
+
//#endregion
|
|
32
|
+
export { MAX_MESSAGE_BYTES, MAX_REQUEST_ID_BYTES, MAX_RESOURCES, MAX_STATEMENT_BYTES, MAX_URI_BYTES, MIN_NONCE_LEN, SiwxError, authenticate, checkExpect, createAccountId, createMessage, eip4361_exports as eip4361, generateNonce, parseAccountId, parseTimestamp, timestampFromDate };
|