protect-mcp 0.7.4 → 0.7.6
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/dist/{chunk-NVJHGXXG.mjs → chunk-6E2DHBAR.mjs} +12 -5
- package/dist/{chunk-36UID5WY.mjs → chunk-JCMDLN5I.mjs} +399 -8
- package/dist/{chunk-LYKNULYU.mjs → chunk-LJQOALYR.mjs} +152 -0
- package/dist/{chunk-KPSICBAJ.mjs → chunk-SETXVE2K.mjs} +2 -2
- package/dist/{chunk-UBZJ3VI2.mjs → chunk-VTPZ4G5I.mjs} +1 -1
- package/dist/{chunk-D2RDY2JR.mjs → chunk-WIPWNWMJ.mjs} +1 -1
- package/dist/{chunk-F2FKQ4XN.mjs → chunk-WV4DKYE4.mjs} +78 -2
- package/dist/cli.js +649 -44
- package/dist/cli.mjs +247 -39
- package/dist/demo-server.js +2 -2
- package/dist/demo-server.mjs +1 -1
- package/dist/{ed25519-DZMMNNVE.mjs → ed25519-BSHMMVNX.mjs} +1 -1
- package/dist/hook-server.js +11 -4
- package/dist/hook-server.mjs +2 -2
- package/dist/{http-transport-4GMMRPW7.mjs → http-transport-JBORN27G.mjs} +3 -3
- package/dist/index.d.mts +46 -12
- package/dist/index.d.ts +46 -12
- package/dist/index.js +1747 -37
- package/dist/index.mjs +1182 -34
- package/dist/{signing-committed-MMLJ6OGG.mjs → signing-committed-QXCW24RF.mjs} +2 -2
- package/package.json +2 -2
- package/policies/agent.cedar +50 -0
package/dist/index.js
CHANGED
|
@@ -28476,8 +28476,8 @@ var require_resolve = __commonJS({
|
|
|
28476
28476
|
}
|
|
28477
28477
|
return count;
|
|
28478
28478
|
}
|
|
28479
|
-
function getFullPath(resolver, id = "",
|
|
28480
|
-
if (
|
|
28479
|
+
function getFullPath(resolver, id = "", normalize2) {
|
|
28480
|
+
if (normalize2 !== false)
|
|
28481
28481
|
id = normalizeId(id);
|
|
28482
28482
|
const p = resolver.parse(id);
|
|
28483
28483
|
return _getFullPath(resolver, p);
|
|
@@ -29817,7 +29817,7 @@ var require_fast_uri = __commonJS({
|
|
|
29817
29817
|
"use strict";
|
|
29818
29818
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
|
|
29819
29819
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
29820
|
-
function
|
|
29820
|
+
function normalize2(uri, options) {
|
|
29821
29821
|
if (typeof uri === "string") {
|
|
29822
29822
|
uri = /** @type {T} */
|
|
29823
29823
|
serialize(parse(uri, options), options);
|
|
@@ -30053,7 +30053,7 @@ var require_fast_uri = __commonJS({
|
|
|
30053
30053
|
}
|
|
30054
30054
|
var fastUri = {
|
|
30055
30055
|
SCHEMES,
|
|
30056
|
-
normalize,
|
|
30056
|
+
normalize: normalize2,
|
|
30057
30057
|
resolve,
|
|
30058
30058
|
resolveComponent,
|
|
30059
30059
|
equal,
|
|
@@ -36227,7 +36227,7 @@ function handleHealth(res, startTime, config) {
|
|
|
36227
36227
|
status: "ok",
|
|
36228
36228
|
uptime_ms: Date.now() - startTime,
|
|
36229
36229
|
mode: config.mode,
|
|
36230
|
-
version:
|
|
36230
|
+
version: process.env.PROTECT_MCP_VERSION || "unknown"
|
|
36231
36231
|
}));
|
|
36232
36232
|
}
|
|
36233
36233
|
function handleStatus(res, logDir) {
|
|
@@ -36999,6 +36999,12 @@ function abytes(b, ...lengths) {
|
|
|
36999
36999
|
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
37000
37000
|
throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
37001
37001
|
}
|
|
37002
|
+
function ahash(h) {
|
|
37003
|
+
if (typeof h !== "function" || typeof h.create !== "function")
|
|
37004
|
+
throw new Error("Hash should be wrapped by utils.createHasher");
|
|
37005
|
+
anumber(h.outputLen);
|
|
37006
|
+
anumber(h.blockLen);
|
|
37007
|
+
}
|
|
37002
37008
|
function aexists(instance, checkFinished = true) {
|
|
37003
37009
|
if (instance.destroyed)
|
|
37004
37010
|
throw new Error("Hash instance has been destroyed");
|
|
@@ -37234,6 +37240,24 @@ var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
|
37234
37240
|
528734635,
|
|
37235
37241
|
1541459225
|
|
37236
37242
|
]);
|
|
37243
|
+
var SHA384_IV = /* @__PURE__ */ Uint32Array.from([
|
|
37244
|
+
3418070365,
|
|
37245
|
+
3238371032,
|
|
37246
|
+
1654270250,
|
|
37247
|
+
914150663,
|
|
37248
|
+
2438529370,
|
|
37249
|
+
812702999,
|
|
37250
|
+
355462360,
|
|
37251
|
+
4144912697,
|
|
37252
|
+
1731405415,
|
|
37253
|
+
4290775857,
|
|
37254
|
+
2394180231,
|
|
37255
|
+
1750603025,
|
|
37256
|
+
3675008525,
|
|
37257
|
+
1694076839,
|
|
37258
|
+
1203062813,
|
|
37259
|
+
3204075428
|
|
37260
|
+
]);
|
|
37237
37261
|
var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
|
|
37238
37262
|
1779033703,
|
|
37239
37263
|
4089235720,
|
|
@@ -37623,8 +37647,30 @@ var SHA512 = class extends HashMD {
|
|
|
37623
37647
|
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
37624
37648
|
}
|
|
37625
37649
|
};
|
|
37650
|
+
var SHA384 = class extends SHA512 {
|
|
37651
|
+
constructor() {
|
|
37652
|
+
super(48);
|
|
37653
|
+
this.Ah = SHA384_IV[0] | 0;
|
|
37654
|
+
this.Al = SHA384_IV[1] | 0;
|
|
37655
|
+
this.Bh = SHA384_IV[2] | 0;
|
|
37656
|
+
this.Bl = SHA384_IV[3] | 0;
|
|
37657
|
+
this.Ch = SHA384_IV[4] | 0;
|
|
37658
|
+
this.Cl = SHA384_IV[5] | 0;
|
|
37659
|
+
this.Dh = SHA384_IV[6] | 0;
|
|
37660
|
+
this.Dl = SHA384_IV[7] | 0;
|
|
37661
|
+
this.Eh = SHA384_IV[8] | 0;
|
|
37662
|
+
this.El = SHA384_IV[9] | 0;
|
|
37663
|
+
this.Fh = SHA384_IV[10] | 0;
|
|
37664
|
+
this.Fl = SHA384_IV[11] | 0;
|
|
37665
|
+
this.Gh = SHA384_IV[12] | 0;
|
|
37666
|
+
this.Gl = SHA384_IV[13] | 0;
|
|
37667
|
+
this.Hh = SHA384_IV[14] | 0;
|
|
37668
|
+
this.Hl = SHA384_IV[15] | 0;
|
|
37669
|
+
}
|
|
37670
|
+
};
|
|
37626
37671
|
var sha256 = /* @__PURE__ */ createHasher(() => new SHA256());
|
|
37627
37672
|
var sha512 = /* @__PURE__ */ createHasher(() => new SHA512());
|
|
37673
|
+
var sha384 = /* @__PURE__ */ createHasher(() => new SHA384());
|
|
37628
37674
|
|
|
37629
37675
|
// node_modules/@noble/curves/esm/utils.js
|
|
37630
37676
|
var _0n = /* @__PURE__ */ BigInt(0);
|
|
@@ -37648,6 +37694,10 @@ function _abytes2(value, length, title = "") {
|
|
|
37648
37694
|
}
|
|
37649
37695
|
return value;
|
|
37650
37696
|
}
|
|
37697
|
+
function numberToHexUnpadded(num) {
|
|
37698
|
+
const hex = num.toString(16);
|
|
37699
|
+
return hex.length & 1 ? "0" + hex : hex;
|
|
37700
|
+
}
|
|
37651
37701
|
function hexToNumber(hex) {
|
|
37652
37702
|
if (typeof hex !== "string")
|
|
37653
37703
|
throw new Error("hex string expected, got " + typeof hex);
|
|
@@ -37710,6 +37760,56 @@ function bitLen(n) {
|
|
|
37710
37760
|
return len;
|
|
37711
37761
|
}
|
|
37712
37762
|
var bitMask = (n) => (_1n << BigInt(n)) - _1n;
|
|
37763
|
+
function createHmacDrbg(hashLen, qByteLen, hmacFn) {
|
|
37764
|
+
if (typeof hashLen !== "number" || hashLen < 2)
|
|
37765
|
+
throw new Error("hashLen must be a number");
|
|
37766
|
+
if (typeof qByteLen !== "number" || qByteLen < 2)
|
|
37767
|
+
throw new Error("qByteLen must be a number");
|
|
37768
|
+
if (typeof hmacFn !== "function")
|
|
37769
|
+
throw new Error("hmacFn must be a function");
|
|
37770
|
+
const u8n = (len) => new Uint8Array(len);
|
|
37771
|
+
const u8of = (byte) => Uint8Array.of(byte);
|
|
37772
|
+
let v = u8n(hashLen);
|
|
37773
|
+
let k = u8n(hashLen);
|
|
37774
|
+
let i = 0;
|
|
37775
|
+
const reset = () => {
|
|
37776
|
+
v.fill(1);
|
|
37777
|
+
k.fill(0);
|
|
37778
|
+
i = 0;
|
|
37779
|
+
};
|
|
37780
|
+
const h = (...b) => hmacFn(k, v, ...b);
|
|
37781
|
+
const reseed = (seed = u8n(0)) => {
|
|
37782
|
+
k = h(u8of(0), seed);
|
|
37783
|
+
v = h();
|
|
37784
|
+
if (seed.length === 0)
|
|
37785
|
+
return;
|
|
37786
|
+
k = h(u8of(1), seed);
|
|
37787
|
+
v = h();
|
|
37788
|
+
};
|
|
37789
|
+
const gen = () => {
|
|
37790
|
+
if (i++ >= 1e3)
|
|
37791
|
+
throw new Error("drbg: tried 1000 values");
|
|
37792
|
+
let len = 0;
|
|
37793
|
+
const out = [];
|
|
37794
|
+
while (len < qByteLen) {
|
|
37795
|
+
v = h();
|
|
37796
|
+
const sl = v.slice();
|
|
37797
|
+
out.push(sl);
|
|
37798
|
+
len += v.length;
|
|
37799
|
+
}
|
|
37800
|
+
return concatBytes(...out);
|
|
37801
|
+
};
|
|
37802
|
+
const genUntil = (seed, pred) => {
|
|
37803
|
+
reset();
|
|
37804
|
+
reseed(seed);
|
|
37805
|
+
let res = void 0;
|
|
37806
|
+
while (!(res = pred(gen())))
|
|
37807
|
+
reseed();
|
|
37808
|
+
reset();
|
|
37809
|
+
return res;
|
|
37810
|
+
};
|
|
37811
|
+
return genUntil;
|
|
37812
|
+
}
|
|
37713
37813
|
function _validateObject(object, fields, optFields = {}) {
|
|
37714
37814
|
if (!object || typeof object !== "object")
|
|
37715
37815
|
throw new Error("expected valid options object");
|
|
@@ -38064,6 +38164,26 @@ function Field(ORDER, bitLenOrOpts, isLE = false, opts = {}) {
|
|
|
38064
38164
|
});
|
|
38065
38165
|
return Object.freeze(f);
|
|
38066
38166
|
}
|
|
38167
|
+
function getFieldBytesLength(fieldOrder) {
|
|
38168
|
+
if (typeof fieldOrder !== "bigint")
|
|
38169
|
+
throw new Error("field order must be bigint");
|
|
38170
|
+
const bitLength = fieldOrder.toString(2).length;
|
|
38171
|
+
return Math.ceil(bitLength / 8);
|
|
38172
|
+
}
|
|
38173
|
+
function getMinHashLength(fieldOrder) {
|
|
38174
|
+
const length = getFieldBytesLength(fieldOrder);
|
|
38175
|
+
return length + Math.ceil(length / 2);
|
|
38176
|
+
}
|
|
38177
|
+
function mapHashToField(key, fieldOrder, isLE = false) {
|
|
38178
|
+
const len = key.length;
|
|
38179
|
+
const fieldLen = getFieldBytesLength(fieldOrder);
|
|
38180
|
+
const minLen = getMinHashLength(fieldOrder);
|
|
38181
|
+
if (len < 16 || len < minLen || len > 1024)
|
|
38182
|
+
throw new Error("expected " + minLen + "-1024 bytes of input, got " + len);
|
|
38183
|
+
const num = isLE ? bytesToNumberLE(key) : bytesToNumberBE(key);
|
|
38184
|
+
const reduced = mod(num, fieldOrder - _1n2) + _1n2;
|
|
38185
|
+
return isLE ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
|
|
38186
|
+
}
|
|
38067
38187
|
|
|
38068
38188
|
// node_modules/@noble/curves/esm/abstract/curve.js
|
|
38069
38189
|
var _0n3 = BigInt(0);
|
|
@@ -38257,6 +38377,21 @@ var wNAF = class {
|
|
|
38257
38377
|
return getW(elm) !== 1;
|
|
38258
38378
|
}
|
|
38259
38379
|
};
|
|
38380
|
+
function mulEndoUnsafe(Point, point, k1, k2) {
|
|
38381
|
+
let acc = point;
|
|
38382
|
+
let p1 = Point.ZERO;
|
|
38383
|
+
let p2 = Point.ZERO;
|
|
38384
|
+
while (k1 > _0n3 || k2 > _0n3) {
|
|
38385
|
+
if (k1 & _1n3)
|
|
38386
|
+
p1 = p1.add(acc);
|
|
38387
|
+
if (k2 & _1n3)
|
|
38388
|
+
p2 = p2.add(acc);
|
|
38389
|
+
acc = acc.double();
|
|
38390
|
+
k1 >>= _1n3;
|
|
38391
|
+
k2 >>= _1n3;
|
|
38392
|
+
}
|
|
38393
|
+
return { p1, p2 };
|
|
38394
|
+
}
|
|
38260
38395
|
function pippenger(c, fieldN, points, scalars) {
|
|
38261
38396
|
validateMSMPoints(points, c);
|
|
38262
38397
|
validateMSMScalars(scalars, fieldN);
|
|
@@ -39216,6 +39351,74 @@ function largestPowerOfTwoLessThan(n) {
|
|
|
39216
39351
|
return k;
|
|
39217
39352
|
}
|
|
39218
39353
|
|
|
39354
|
+
// node_modules/@noble/hashes/esm/hmac.js
|
|
39355
|
+
var HMAC = class extends Hash {
|
|
39356
|
+
constructor(hash, _key) {
|
|
39357
|
+
super();
|
|
39358
|
+
this.finished = false;
|
|
39359
|
+
this.destroyed = false;
|
|
39360
|
+
ahash(hash);
|
|
39361
|
+
const key = toBytes(_key);
|
|
39362
|
+
this.iHash = hash.create();
|
|
39363
|
+
if (typeof this.iHash.update !== "function")
|
|
39364
|
+
throw new Error("Expected instance of class which extends utils.Hash");
|
|
39365
|
+
this.blockLen = this.iHash.blockLen;
|
|
39366
|
+
this.outputLen = this.iHash.outputLen;
|
|
39367
|
+
const blockLen = this.blockLen;
|
|
39368
|
+
const pad = new Uint8Array(blockLen);
|
|
39369
|
+
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
|
|
39370
|
+
for (let i = 0; i < pad.length; i++)
|
|
39371
|
+
pad[i] ^= 54;
|
|
39372
|
+
this.iHash.update(pad);
|
|
39373
|
+
this.oHash = hash.create();
|
|
39374
|
+
for (let i = 0; i < pad.length; i++)
|
|
39375
|
+
pad[i] ^= 54 ^ 92;
|
|
39376
|
+
this.oHash.update(pad);
|
|
39377
|
+
clean(pad);
|
|
39378
|
+
}
|
|
39379
|
+
update(buf) {
|
|
39380
|
+
aexists(this);
|
|
39381
|
+
this.iHash.update(buf);
|
|
39382
|
+
return this;
|
|
39383
|
+
}
|
|
39384
|
+
digestInto(out) {
|
|
39385
|
+
aexists(this);
|
|
39386
|
+
abytes(out, this.outputLen);
|
|
39387
|
+
this.finished = true;
|
|
39388
|
+
this.iHash.digestInto(out);
|
|
39389
|
+
this.oHash.update(out);
|
|
39390
|
+
this.oHash.digestInto(out);
|
|
39391
|
+
this.destroy();
|
|
39392
|
+
}
|
|
39393
|
+
digest() {
|
|
39394
|
+
const out = new Uint8Array(this.oHash.outputLen);
|
|
39395
|
+
this.digestInto(out);
|
|
39396
|
+
return out;
|
|
39397
|
+
}
|
|
39398
|
+
_cloneInto(to) {
|
|
39399
|
+
to || (to = Object.create(Object.getPrototypeOf(this), {}));
|
|
39400
|
+
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
|
|
39401
|
+
to = to;
|
|
39402
|
+
to.finished = finished;
|
|
39403
|
+
to.destroyed = destroyed;
|
|
39404
|
+
to.blockLen = blockLen;
|
|
39405
|
+
to.outputLen = outputLen;
|
|
39406
|
+
to.oHash = oHash._cloneInto(to.oHash);
|
|
39407
|
+
to.iHash = iHash._cloneInto(to.iHash);
|
|
39408
|
+
return to;
|
|
39409
|
+
}
|
|
39410
|
+
clone() {
|
|
39411
|
+
return this._cloneInto();
|
|
39412
|
+
}
|
|
39413
|
+
destroy() {
|
|
39414
|
+
this.destroyed = true;
|
|
39415
|
+
this.oHash.destroy();
|
|
39416
|
+
this.iHash.destroy();
|
|
39417
|
+
}
|
|
39418
|
+
};
|
|
39419
|
+
var hmac = (hash, key, message) => new HMAC(hash, key).update(message).digest();
|
|
39420
|
+
hmac.create = (hash, key) => new HMAC(hash, key);
|
|
39421
|
+
|
|
39219
39422
|
// src/commitments/primitives.ts
|
|
39220
39423
|
function jcs(value) {
|
|
39221
39424
|
if (value === null || value === void 0) return "null";
|
|
@@ -40815,7 +41018,7 @@ async function startHookServer(options = {}) {
|
|
|
40815
41018
|
res.end(JSON.stringify({
|
|
40816
41019
|
status: "ok",
|
|
40817
41020
|
server: "protect-mcp-hooks",
|
|
40818
|
-
version:
|
|
41021
|
+
version: process.env.PROTECT_MCP_VERSION || "unknown",
|
|
40819
41022
|
uptime_ms: Date.now() - state.startTime,
|
|
40820
41023
|
mode: enforce ? "enforce" : "shadow",
|
|
40821
41024
|
policy_digest: policyDigest,
|
|
@@ -40902,9 +41105,10 @@ async function startHookServer(options = {}) {
|
|
|
40902
41105
|
const pad = (s, n = 46) => s.padEnd(n);
|
|
40903
41106
|
w(`
|
|
40904
41107
|
`);
|
|
40905
|
-
w(` protect-mcp
|
|
41108
|
+
w(process.env.PROTECT_MCP_VERSION ? ` protect-mcp v${process.env.PROTECT_MCP_VERSION}
|
|
41109
|
+
` : ` protect-mcp
|
|
40906
41110
|
`);
|
|
40907
|
-
w(` ScopeBlind \
|
|
41111
|
+
w(` ScopeBlind \xB7 https://scopeblind.com
|
|
40908
41112
|
`);
|
|
40909
41113
|
w(`
|
|
40910
41114
|
`);
|
|
@@ -40932,7 +41136,13 @@ async function startHookServer(options = {}) {
|
|
|
40932
41136
|
`);
|
|
40933
41137
|
w(`
|
|
40934
41138
|
`);
|
|
40935
|
-
w(` deny is authoritative
|
|
41139
|
+
w(` deny is authoritative: it cannot be overridden.
|
|
41140
|
+
`);
|
|
41141
|
+
w(`
|
|
41142
|
+
`);
|
|
41143
|
+
w(` See your record npx protect-mcp record
|
|
41144
|
+
`);
|
|
41145
|
+
w(` a searchable view of every decision, all on this machine
|
|
40936
41146
|
`);
|
|
40937
41147
|
w(`
|
|
40938
41148
|
`);
|
|
@@ -41812,6 +42022,292 @@ var defaultPermit2 = `
|
|
|
41812
42022
|
// Default posture: observe all non-matching tools so the connector can be piloted in shadow mode.
|
|
41813
42023
|
permit(principal, action == Action::"MCP::Tool::call", resource);
|
|
41814
42024
|
`;
|
|
42025
|
+
var nautilusBridgePy = String.raw`#!/usr/bin/env python3
|
|
42026
|
+
"""
|
|
42027
|
+
ScopeBlind external bridge for NautilusTrader-compatible pilots.
|
|
42028
|
+
|
|
42029
|
+
This file is intentionally outside NautilusTrader. It gives protect-mcp a stable
|
|
42030
|
+
JSONL command boundary for staging, approval-gated submission, cancellation, and
|
|
42031
|
+
event export while keeping the trading engine customer-owned.
|
|
42032
|
+
|
|
42033
|
+
Mock mode runs without NautilusTrader installed. Real mode is enabled by setting
|
|
42034
|
+
NAUTILUS_BRIDGE_MODULE to "module.path:ClassName"; the class may implement:
|
|
42035
|
+
submit_order(order), modify_order(order), cancel_order(order), reconcile(order),
|
|
42036
|
+
export_events(since=None)
|
|
42037
|
+
"""
|
|
42038
|
+
|
|
42039
|
+
from __future__ import annotations
|
|
42040
|
+
|
|
42041
|
+
import hashlib
|
|
42042
|
+
import importlib
|
|
42043
|
+
import json
|
|
42044
|
+
import os
|
|
42045
|
+
import sys
|
|
42046
|
+
import time
|
|
42047
|
+
from dataclasses import dataclass, field
|
|
42048
|
+
from pathlib import Path
|
|
42049
|
+
from typing import Any, Callable
|
|
42050
|
+
|
|
42051
|
+
|
|
42052
|
+
def canonical_json(value: Any) -> str:
|
|
42053
|
+
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
|
42054
|
+
|
|
42055
|
+
|
|
42056
|
+
def sha256_json(value: Any) -> str:
|
|
42057
|
+
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
|
|
42058
|
+
|
|
42059
|
+
|
|
42060
|
+
def now_ms() -> int:
|
|
42061
|
+
return int(time.time() * 1000)
|
|
42062
|
+
|
|
42063
|
+
|
|
42064
|
+
@dataclass
|
|
42065
|
+
class BridgeState:
|
|
42066
|
+
root: Path = field(default_factory=lambda: Path(os.environ.get("SCOPEBLIND_NAUTILUS_STATE_DIR", ".protect-mcp/nautilus")))
|
|
42067
|
+
|
|
42068
|
+
def __post_init__(self) -> None:
|
|
42069
|
+
self.root.mkdir(parents=True, exist_ok=True)
|
|
42070
|
+
self.orders_path.touch(exist_ok=True)
|
|
42071
|
+
self.events_path.touch(exist_ok=True)
|
|
42072
|
+
|
|
42073
|
+
@property
|
|
42074
|
+
def orders_path(self) -> Path:
|
|
42075
|
+
return self.root / "orders.jsonl"
|
|
42076
|
+
|
|
42077
|
+
@property
|
|
42078
|
+
def events_path(self) -> Path:
|
|
42079
|
+
return self.root / "events.jsonl"
|
|
42080
|
+
|
|
42081
|
+
def append_order(self, order: dict[str, Any]) -> None:
|
|
42082
|
+
with self.orders_path.open("a", encoding="utf-8") as handle:
|
|
42083
|
+
handle.write(canonical_json(order) + "\n")
|
|
42084
|
+
|
|
42085
|
+
def append_event(self, event: dict[str, Any]) -> dict[str, Any]:
|
|
42086
|
+
enriched = {
|
|
42087
|
+
"event_id": event.get("event_id") or f"nt-{now_ms()}-{len(event)}",
|
|
42088
|
+
"observed_at_ms": now_ms(),
|
|
42089
|
+
**event,
|
|
42090
|
+
}
|
|
42091
|
+
enriched["event_digest"] = sha256_json(enriched)
|
|
42092
|
+
with self.events_path.open("a", encoding="utf-8") as handle:
|
|
42093
|
+
handle.write(canonical_json(enriched) + "\n")
|
|
42094
|
+
return enriched
|
|
42095
|
+
|
|
42096
|
+
def events(self) -> list[dict[str, Any]]:
|
|
42097
|
+
rows: list[dict[str, Any]] = []
|
|
42098
|
+
with self.events_path.open("r", encoding="utf-8") as handle:
|
|
42099
|
+
for line in handle:
|
|
42100
|
+
if line.strip():
|
|
42101
|
+
rows.append(json.loads(line))
|
|
42102
|
+
return rows
|
|
42103
|
+
|
|
42104
|
+
|
|
42105
|
+
class ScopeBlindNautilusBridge:
|
|
42106
|
+
def __init__(self) -> None:
|
|
42107
|
+
self.state = BridgeState()
|
|
42108
|
+
self.real = self._load_real_bridge()
|
|
42109
|
+
|
|
42110
|
+
def _load_real_bridge(self) -> Any | None:
|
|
42111
|
+
target = os.environ.get("NAUTILUS_BRIDGE_MODULE")
|
|
42112
|
+
if not target:
|
|
42113
|
+
return None
|
|
42114
|
+
module_name, _, class_name = target.partition(":")
|
|
42115
|
+
if not module_name or not class_name:
|
|
42116
|
+
raise ValueError("NAUTILUS_BRIDGE_MODULE must be module.path:ClassName")
|
|
42117
|
+
module = importlib.import_module(module_name)
|
|
42118
|
+
return getattr(module, class_name)()
|
|
42119
|
+
|
|
42120
|
+
def handle(self, command: dict[str, Any]) -> dict[str, Any]:
|
|
42121
|
+
action = command.get("action")
|
|
42122
|
+
handlers: dict[str, Callable[[dict[str, Any]], dict[str, Any]]] = {
|
|
42123
|
+
"stage_order": self.stage_order,
|
|
42124
|
+
"submit_order": self.submit_order,
|
|
42125
|
+
"modify_order": self.modify_order,
|
|
42126
|
+
"cancel_order": self.cancel_order,
|
|
42127
|
+
"reconcile": self.reconcile,
|
|
42128
|
+
"export_events": self.export_events,
|
|
42129
|
+
}
|
|
42130
|
+
if action not in handlers:
|
|
42131
|
+
return self.error(command, "unknown_action", f"Unsupported action: {action}")
|
|
42132
|
+
try:
|
|
42133
|
+
return handlers[action](command)
|
|
42134
|
+
except Exception as exc:
|
|
42135
|
+
return self.error(command, "bridge_error", str(exc))
|
|
42136
|
+
|
|
42137
|
+
def require(self, command: dict[str, Any], *fields: str) -> None:
|
|
42138
|
+
missing = [field for field in fields if command.get(field) in (None, "")]
|
|
42139
|
+
if missing:
|
|
42140
|
+
raise ValueError(f"missing required field(s): {', '.join(missing)}")
|
|
42141
|
+
|
|
42142
|
+
def require_approved(self, command: dict[str, Any]) -> None:
|
|
42143
|
+
self.require(command, "approval_receipt")
|
|
42144
|
+
if command.get("mandate_passed") is not True:
|
|
42145
|
+
raise ValueError("mandate_passed must be true before live order mutation")
|
|
42146
|
+
|
|
42147
|
+
def stage_order(self, command: dict[str, Any]) -> dict[str, Any]:
|
|
42148
|
+
self.require(command, "client_order_id", "instrument_id", "side", "quantity")
|
|
42149
|
+
order = self.order_projection(command, status="staged")
|
|
42150
|
+
self.state.append_order(order)
|
|
42151
|
+
event = self.state.append_event({
|
|
42152
|
+
"type": "scopeblind.nautilus.order_staged.v1",
|
|
42153
|
+
"client_order_id": order["client_order_id"],
|
|
42154
|
+
"order_digest": sha256_json(order),
|
|
42155
|
+
"disclosure": "position_blind",
|
|
42156
|
+
})
|
|
42157
|
+
return self.ok(command, {"status": "staged", "order": order, "event": event})
|
|
42158
|
+
|
|
42159
|
+
def submit_order(self, command: dict[str, Any]) -> dict[str, Any]:
|
|
42160
|
+
self.require_approved(command)
|
|
42161
|
+
order = self.order_projection(command, status="submitted")
|
|
42162
|
+
if self.real and hasattr(self.real, "submit_order"):
|
|
42163
|
+
external = self.real.submit_order(order)
|
|
42164
|
+
else:
|
|
42165
|
+
external = {"mode": "mock", "external_order_id": f"MOCK-{order['client_order_id']}"}
|
|
42166
|
+
event = self.state.append_event({
|
|
42167
|
+
"type": "scopeblind.nautilus.order_submitted.v1",
|
|
42168
|
+
"client_order_id": order["client_order_id"],
|
|
42169
|
+
"order_digest": sha256_json(order),
|
|
42170
|
+
"external_digest": sha256_json(external),
|
|
42171
|
+
"disclosure": "position_blind",
|
|
42172
|
+
})
|
|
42173
|
+
return self.ok(command, {"status": "submitted", "order": order, "external": external, "event": event})
|
|
42174
|
+
|
|
42175
|
+
def modify_order(self, command: dict[str, Any]) -> dict[str, Any]:
|
|
42176
|
+
self.require_approved(command)
|
|
42177
|
+
self.require(command, "client_order_id")
|
|
42178
|
+
if self.real and hasattr(self.real, "modify_order"):
|
|
42179
|
+
external = self.real.modify_order(command)
|
|
42180
|
+
else:
|
|
42181
|
+
external = {"mode": "mock", "modified": command["client_order_id"]}
|
|
42182
|
+
event = self.state.append_event({
|
|
42183
|
+
"type": "scopeblind.nautilus.order_modified.v1",
|
|
42184
|
+
"client_order_id": command["client_order_id"],
|
|
42185
|
+
"command_digest": sha256_json(command),
|
|
42186
|
+
"external_digest": sha256_json(external),
|
|
42187
|
+
"disclosure": "position_blind",
|
|
42188
|
+
})
|
|
42189
|
+
return self.ok(command, {"status": "modified", "external": external, "event": event})
|
|
42190
|
+
|
|
42191
|
+
def cancel_order(self, command: dict[str, Any]) -> dict[str, Any]:
|
|
42192
|
+
self.require_approved(command)
|
|
42193
|
+
self.require(command, "client_order_id")
|
|
42194
|
+
if self.real and hasattr(self.real, "cancel_order"):
|
|
42195
|
+
external = self.real.cancel_order(command)
|
|
42196
|
+
else:
|
|
42197
|
+
external = {"mode": "mock", "cancelled": command["client_order_id"]}
|
|
42198
|
+
event = self.state.append_event({
|
|
42199
|
+
"type": "scopeblind.nautilus.order_cancelled.v1",
|
|
42200
|
+
"client_order_id": command["client_order_id"],
|
|
42201
|
+
"command_digest": sha256_json(command),
|
|
42202
|
+
"external_digest": sha256_json(external),
|
|
42203
|
+
"disclosure": "position_blind",
|
|
42204
|
+
})
|
|
42205
|
+
return self.ok(command, {"status": "cancelled", "external": external, "event": event})
|
|
42206
|
+
|
|
42207
|
+
def reconcile(self, command: dict[str, Any]) -> dict[str, Any]:
|
|
42208
|
+
self.require(command, "client_order_id")
|
|
42209
|
+
if self.real and hasattr(self.real, "reconcile"):
|
|
42210
|
+
external = self.real.reconcile(command)
|
|
42211
|
+
else:
|
|
42212
|
+
external = {"mode": "mock", "client_order_id": command["client_order_id"], "state": "accepted"}
|
|
42213
|
+
event = self.state.append_event({
|
|
42214
|
+
"type": "scopeblind.nautilus.reconciled.v1",
|
|
42215
|
+
"client_order_id": command["client_order_id"],
|
|
42216
|
+
"external_digest": sha256_json(external),
|
|
42217
|
+
"disclosure": "position_blind",
|
|
42218
|
+
})
|
|
42219
|
+
return self.ok(command, {"status": "reconciled", "external": external, "event": event})
|
|
42220
|
+
|
|
42221
|
+
def export_events(self, command: dict[str, Any]) -> dict[str, Any]:
|
|
42222
|
+
if self.real and hasattr(self.real, "export_events"):
|
|
42223
|
+
external_events = self.real.export_events(command.get("since"))
|
|
42224
|
+
else:
|
|
42225
|
+
external_events = self.state.events()
|
|
42226
|
+
return self.ok(command, {
|
|
42227
|
+
"status": "exported",
|
|
42228
|
+
"event_count": len(external_events),
|
|
42229
|
+
"commitment_root": sha256_json(external_events),
|
|
42230
|
+
"events": external_events,
|
|
42231
|
+
})
|
|
42232
|
+
|
|
42233
|
+
def order_projection(self, command: dict[str, Any], status: str) -> dict[str, Any]:
|
|
42234
|
+
return {
|
|
42235
|
+
"client_order_id": command["client_order_id"],
|
|
42236
|
+
"instrument_id": command["instrument_id"],
|
|
42237
|
+
"side": command["side"],
|
|
42238
|
+
"quantity": command["quantity"],
|
|
42239
|
+
"price": command.get("price"),
|
|
42240
|
+
"time_in_force": command.get("time_in_force", "GTC"),
|
|
42241
|
+
"strategy_id": command.get("strategy_id"),
|
|
42242
|
+
"mandate_digest": command.get("mandate_digest"),
|
|
42243
|
+
"approval_receipt": command.get("approval_receipt"),
|
|
42244
|
+
"status": status,
|
|
42245
|
+
"created_at_ms": now_ms(),
|
|
42246
|
+
}
|
|
42247
|
+
|
|
42248
|
+
def ok(self, command: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]:
|
|
42249
|
+
return {
|
|
42250
|
+
"ok": True,
|
|
42251
|
+
"bridge": "scopeblind.nautilus.external.v1",
|
|
42252
|
+
"mode": "real" if self.real else "mock",
|
|
42253
|
+
"request_digest": sha256_json(command),
|
|
42254
|
+
**result,
|
|
42255
|
+
}
|
|
42256
|
+
|
|
42257
|
+
def error(self, command: dict[str, Any], code: str, message: str) -> dict[str, Any]:
|
|
42258
|
+
return {
|
|
42259
|
+
"ok": False,
|
|
42260
|
+
"bridge": "scopeblind.nautilus.external.v1",
|
|
42261
|
+
"mode": "real" if self.real else "mock",
|
|
42262
|
+
"error": {"code": code, "message": message},
|
|
42263
|
+
"request_digest": sha256_json(command),
|
|
42264
|
+
}
|
|
42265
|
+
|
|
42266
|
+
|
|
42267
|
+
def main() -> int:
|
|
42268
|
+
bridge = ScopeBlindNautilusBridge()
|
|
42269
|
+
for line in sys.stdin:
|
|
42270
|
+
if not line.strip():
|
|
42271
|
+
continue
|
|
42272
|
+
command = json.loads(line)
|
|
42273
|
+
print(canonical_json(bridge.handle(command)), flush=True)
|
|
42274
|
+
return 0
|
|
42275
|
+
|
|
42276
|
+
|
|
42277
|
+
if __name__ == "__main__":
|
|
42278
|
+
raise SystemExit(main())
|
|
42279
|
+
`;
|
|
42280
|
+
var nautilusAdapterReadme = `# NautilusTrader-compatible external bridge
|
|
42281
|
+
|
|
42282
|
+
This connector is intentionally external to NautilusTrader. It lets protect-mcp
|
|
42283
|
+
control and receipt high-risk order actions while a customer-owned Nautilus
|
|
42284
|
+
process remains the trading engine.
|
|
42285
|
+
|
|
42286
|
+
## Local mock run
|
|
42287
|
+
|
|
42288
|
+
\`\`\`bash
|
|
42289
|
+
python3 .protect-mcp/connectors/nautilus-trader/bridge.py <<'JSONL'
|
|
42290
|
+
{"action":"stage_order","client_order_id":"SB-1","instrument_id":"AAPL.NASDAQ","side":"BUY","quantity":"50","price":"182.40","mandate_digest":"demo"}
|
|
42291
|
+
{"action":"submit_order","client_order_id":"SB-1","instrument_id":"AAPL.NASDAQ","side":"BUY","quantity":"50","price":"182.40","mandate_digest":"demo","mandate_passed":true,"approval_receipt":"receipt-demo"}
|
|
42292
|
+
{"action":"export_events"}
|
|
42293
|
+
JSONL
|
|
42294
|
+
\`\`\`
|
|
42295
|
+
|
|
42296
|
+
## Real mode
|
|
42297
|
+
|
|
42298
|
+
Set \`NAUTILUS_BRIDGE_MODULE=customer_module:BridgeClass\`. The class can
|
|
42299
|
+
implement \`submit_order\`, \`modify_order\`, \`cancel_order\`, \`reconcile\`,
|
|
42300
|
+
and \`export_events\`. Keep that glue in the customer's repository so Nautilus
|
|
42301
|
+
licensing, credentials, and trading logic stay outside ScopeBlind.
|
|
42302
|
+
|
|
42303
|
+
## Upstream contribution posture
|
|
42304
|
+
|
|
42305
|
+
The best NautilusTrader contribution is not this bridge or a UI. It is a small,
|
|
42306
|
+
vendor-neutral audit/event sink RFC: a documented way to export normalized order
|
|
42307
|
+
commands, execution reports, fills, cancels, and reconciliation events so
|
|
42308
|
+
external compliance wrappers can prove what happened without mutating the
|
|
42309
|
+
engine.
|
|
42310
|
+
`;
|
|
41815
42311
|
var CONNECTOR_PILOTS = [
|
|
41816
42312
|
{
|
|
41817
42313
|
id: "github",
|
|
@@ -42015,6 +42511,93 @@ when { ["pms.order.stage", "pms.order.book", "pms.order.cancel"].contains(contex
|
|
|
42015
42511
|
|
|
42016
42512
|
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
42017
42513
|
when { context.tool == "pms.order.book" && context.mandate_passed != true };
|
|
42514
|
+
`
|
|
42515
|
+
},
|
|
42516
|
+
{
|
|
42517
|
+
id: "nautilus-trader",
|
|
42518
|
+
category: "finance",
|
|
42519
|
+
name: "NautilusTrader-compatible external bridge",
|
|
42520
|
+
status: "usable-pilot",
|
|
42521
|
+
description: "Controls NautilusTrader-compatible staged orders through an external JSONL bridge, with local mock mode and customer-owned real mode.",
|
|
42522
|
+
value: "Turns Nautilus into a strong Legate demo target: mandate-check, exact approval, external order event, position-blind audit bundle, and later reconciliation.",
|
|
42523
|
+
env: [
|
|
42524
|
+
{ name: "NAUTILUS_BRIDGE_MODULE", required: false, description: "Optional customer glue in module.path:ClassName form for real Nautilus submission." },
|
|
42525
|
+
{ name: "SCOPEBLIND_NAUTILUS_STATE_DIR", required: false, description: "Optional state directory for local mock events. Defaults to .protect-mcp/nautilus." },
|
|
42526
|
+
{ name: "NAUTILUS_TRADER_PROJECT", required: false, description: "Optional path to the customer Nautilus project when running real mode." }
|
|
42527
|
+
],
|
|
42528
|
+
tools: [
|
|
42529
|
+
"nautilus.order.stage",
|
|
42530
|
+
"nautilus.order.submit",
|
|
42531
|
+
"nautilus.order.modify",
|
|
42532
|
+
"nautilus.order.cancel",
|
|
42533
|
+
"nautilus.strategy.deploy",
|
|
42534
|
+
"nautilus.event.export",
|
|
42535
|
+
"nautilus.reconcile"
|
|
42536
|
+
],
|
|
42537
|
+
actions: [
|
|
42538
|
+
{ name: "Stage order", tool: "nautilus.order.stage", risk: "medium", mode: "require_approval", description: "Creates a position-blind booking intent and event commitment." },
|
|
42539
|
+
{ name: "Submit order", tool: "nautilus.order.submit", risk: "high", mode: "require_approval", description: "Requires mandate pass plus exact approval before live order mutation." },
|
|
42540
|
+
{ name: "Modify or cancel order", tool: "nautilus.order.modify", risk: "high", mode: "require_approval", description: "Mutates live order state and must carry a fresh approval receipt." },
|
|
42541
|
+
{ name: "Deploy strategy", tool: "nautilus.strategy.deploy", risk: "high", mode: "require_approval", description: "Requires signed strategy pack, mandate scope, and operator approval." },
|
|
42542
|
+
{ name: "Export event log", tool: "nautilus.event.export", risk: "low", mode: "observe", description: "Exports normalized event commitments for receipt corroboration." }
|
|
42543
|
+
],
|
|
42544
|
+
setup: [
|
|
42545
|
+
"Run mock mode first: protect-mcp connectors init nautilus-trader --force.",
|
|
42546
|
+
"Pipe stage/submit/reconcile JSONL through .protect-mcp/connectors/nautilus-trader/bridge.py.",
|
|
42547
|
+
"For real mode, set NAUTILUS_BRIDGE_MODULE to customer-owned glue that calls NautilusTrader APIs.",
|
|
42548
|
+
"Open an upstream NautilusTrader RFC for a neutral audit/event sink before proposing any PR."
|
|
42549
|
+
],
|
|
42550
|
+
config: {
|
|
42551
|
+
type: "scopeblind.connector_pilot.v1",
|
|
42552
|
+
provider: "nautilus-trader-compatible",
|
|
42553
|
+
mode: "external-bridge-mock-first",
|
|
42554
|
+
license_boundary: "No NautilusTrader code is bundled. Real mode calls a customer-owned process/module.",
|
|
42555
|
+
adapter_contract: {
|
|
42556
|
+
protocol: "stdin/stdout JSONL",
|
|
42557
|
+
bridge: ".protect-mcp/connectors/nautilus-trader/bridge.py",
|
|
42558
|
+
real_mode_env: "NAUTILUS_BRIDGE_MODULE=module.path:ClassName",
|
|
42559
|
+
actions: ["stage_order", "submit_order", "modify_order", "cancel_order", "reconcile", "export_events"]
|
|
42560
|
+
},
|
|
42561
|
+
controlled_tools: [
|
|
42562
|
+
"nautilus.order.stage",
|
|
42563
|
+
"nautilus.order.submit",
|
|
42564
|
+
"nautilus.order.modify",
|
|
42565
|
+
"nautilus.order.cancel",
|
|
42566
|
+
"nautilus.strategy.deploy",
|
|
42567
|
+
"nautilus.event.export",
|
|
42568
|
+
"nautilus.reconcile"
|
|
42569
|
+
],
|
|
42570
|
+
approval_required_for: ["submit_order", "modify_order", "cancel_order", "strategy_deploy"],
|
|
42571
|
+
receipt_fields: [
|
|
42572
|
+
"client_order_id",
|
|
42573
|
+
"instrument_id_hash",
|
|
42574
|
+
"side",
|
|
42575
|
+
"quantity",
|
|
42576
|
+
"price",
|
|
42577
|
+
"mandate_digest",
|
|
42578
|
+
"approval_receipt",
|
|
42579
|
+
"external_event_digest",
|
|
42580
|
+
"commitment_root"
|
|
42581
|
+
],
|
|
42582
|
+
upstream_rfc: {
|
|
42583
|
+
title: "[RFC] Add a vendor-neutral order/execution audit event sink",
|
|
42584
|
+
non_goals: ["ScopeBlind dependency", "UI dashboard", "AI tooling", "new venue adapter"]
|
|
42585
|
+
}
|
|
42586
|
+
},
|
|
42587
|
+
artifacts: [
|
|
42588
|
+
{ path: "nautilus-trader/bridge.py", contents: nautilusBridgePy, executable: true },
|
|
42589
|
+
{ path: "nautilus-trader/README.md", contents: nautilusAdapterReadme }
|
|
42590
|
+
],
|
|
42591
|
+
cedar: `${defaultPermit2}
|
|
42592
|
+
// NautilusTrader-compatible pilot: stage can be observed, but any live mutation requires exact approval.
|
|
42593
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
42594
|
+
when { ["nautilus.order.submit", "nautilus.order.modify", "nautilus.order.cancel", "nautilus.strategy.deploy"].contains(context.tool) && !context.approved };
|
|
42595
|
+
|
|
42596
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
42597
|
+
when { ["nautilus.order.submit", "nautilus.order.modify", "nautilus.order.cancel"].contains(context.tool) && context.mandate_passed != true };
|
|
42598
|
+
|
|
42599
|
+
forbid(principal, action == Action::"MCP::Tool::call", resource)
|
|
42600
|
+
when { context.tool == "nautilus.strategy.deploy" && context.strategy_pack_signed != true };
|
|
42018
42601
|
`
|
|
42019
42602
|
}
|
|
42020
42603
|
];
|
|
@@ -42046,11 +42629,26 @@ function writeConnectorPilots(opts) {
|
|
|
42046
42629
|
(0, import_node_fs10.writeFileSync)(policyPath, pilot.cedar.endsWith("\n") ? pilot.cedar : `${pilot.cedar}
|
|
42047
42630
|
`);
|
|
42048
42631
|
written.push(configPath, policyPath);
|
|
42632
|
+
for (const artifact of pilot.artifacts || []) {
|
|
42633
|
+
const artifactPath = connectorArtifactPath(directory, artifact.path);
|
|
42634
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path6.dirname)(artifactPath), { recursive: true });
|
|
42635
|
+
(0, import_node_fs10.writeFileSync)(artifactPath, artifact.contents.endsWith("\n") ? artifact.contents : `${artifact.contents}
|
|
42636
|
+
`);
|
|
42637
|
+
if (artifact.executable) (0, import_node_fs10.chmodSync)(artifactPath, 493);
|
|
42638
|
+
written.push(artifactPath);
|
|
42639
|
+
}
|
|
42049
42640
|
}
|
|
42050
42641
|
(0, import_node_fs10.writeFileSync)((0, import_node_path6.join)(directory, "README.md"), renderConnectorReadme(selected));
|
|
42051
42642
|
written.push((0, import_node_path6.join)(directory, "README.md"));
|
|
42052
42643
|
return { written, pilots: selected, directory };
|
|
42053
42644
|
}
|
|
42645
|
+
function connectorArtifactPath(directory, relativePath) {
|
|
42646
|
+
const clean2 = (0, import_node_path6.normalize)(relativePath).replace(/^(\.\.(\/|\\|$))+/, "");
|
|
42647
|
+
if (clean2.startsWith("/") || clean2.includes("..")) {
|
|
42648
|
+
throw new Error(`Unsafe connector artifact path: ${relativePath}`);
|
|
42649
|
+
}
|
|
42650
|
+
return (0, import_node_path6.join)(directory, clean2);
|
|
42651
|
+
}
|
|
42054
42652
|
function readInstalledConnectorPilots(dir) {
|
|
42055
42653
|
const directory = connectorDirectory(dir);
|
|
42056
42654
|
if (!(0, import_node_fs10.existsSync)(directory)) return [];
|
|
@@ -42084,15 +42682,15 @@ function connectorDoctor(dir, env = process.env) {
|
|
|
42084
42682
|
}));
|
|
42085
42683
|
const missingRequired = envRows.filter((item) => item.required && !item.present).map((item) => item.name);
|
|
42086
42684
|
const optionalPresent = envRows.filter((item) => !item.required && item.present).map((item) => item.name);
|
|
42087
|
-
const optionalProviderReady = pilot.id === "slack-teams" ? Boolean(env.SLACK_BOT_TOKEN || env.TEAMS_WEBHOOK_URL) : pilot.id === "finance-pms" ? Boolean(env.PMS_ADAPTER_URL) : false;
|
|
42088
|
-
const mockModeReady = pilot.id === "finance-pms";
|
|
42685
|
+
const optionalProviderReady = pilot.id === "slack-teams" ? Boolean(env.SLACK_BOT_TOKEN || env.TEAMS_WEBHOOK_URL) : pilot.id === "finance-pms" ? Boolean(env.PMS_ADAPTER_URL) : pilot.id === "nautilus-trader" ? Boolean(env.NAUTILUS_BRIDGE_MODULE || env.NAUTILUS_TRADER_PROJECT) : false;
|
|
42686
|
+
const mockModeReady = pilot.id === "finance-pms" || pilot.id === "nautilus-trader";
|
|
42089
42687
|
return {
|
|
42090
42688
|
id: pilot.id,
|
|
42091
42689
|
name: pilot.name,
|
|
42092
42690
|
category: pilot.category,
|
|
42093
42691
|
installed: installed.has(pilot.id),
|
|
42094
42692
|
usable: missingRequired.length === 0 && (pilot.env.some((item) => item.required) || pilot.env.length === 0 || optionalProviderReady || mockModeReady),
|
|
42095
|
-
mode: pilot.id === "finance-pms" && !env.PMS_ADAPTER_URL ? "mock" : pilot.id === "slack-teams" && !env.SLACK_BOT_TOKEN && !env.TEAMS_WEBHOOK_URL ? "needs_provider_choice" : "configured_or_local",
|
|
42693
|
+
mode: pilot.id === "finance-pms" && !env.PMS_ADAPTER_URL ? "mock" : pilot.id === "nautilus-trader" && !env.NAUTILUS_BRIDGE_MODULE ? "mock_bridge" : pilot.id === "slack-teams" && !env.SLACK_BOT_TOKEN && !env.TEAMS_WEBHOOK_URL ? "needs_provider_choice" : "configured_or_local",
|
|
42096
42694
|
missing_required: missingRequired,
|
|
42097
42695
|
optional_present: optionalPresent,
|
|
42098
42696
|
tools: pilot.tools,
|
|
@@ -42115,7 +42713,10 @@ Tools: ${pilot.tools.map((tool) => `\`${tool}\``).join(", ")}
|
|
|
42115
42713
|
|
|
42116
42714
|
Setup:
|
|
42117
42715
|
${pilot.setup.map((step) => `- ${step}`).join("\n")}
|
|
42118
|
-
`
|
|
42716
|
+
${pilot.artifacts?.length ? `
|
|
42717
|
+
Generated files:
|
|
42718
|
+
${pilot.artifacts.map((artifact) => `- \`${artifact.path}\``).join("\n")}
|
|
42719
|
+
` : ""}`).join("\n")}
|
|
42119
42720
|
Next: run \`npx protect-mcp dashboard --open\` and review tool inventory, policy coverage, approvals, and receipts.
|
|
42120
42721
|
`;
|
|
42121
42722
|
}
|
|
@@ -42437,6 +43038,1066 @@ MIT
|
|
|
42437
43038
|
|
|
42438
43039
|
// src/webauthn-approval.ts
|
|
42439
43040
|
var import_node_crypto8 = require("crypto");
|
|
43041
|
+
|
|
43042
|
+
// node_modules/@noble/curves/esm/abstract/weierstrass.js
|
|
43043
|
+
var divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n4) / den;
|
|
43044
|
+
function _splitEndoScalar(k, basis, n) {
|
|
43045
|
+
const [[a1, b1], [a2, b2]] = basis;
|
|
43046
|
+
const c1 = divNearest(b2 * k, n);
|
|
43047
|
+
const c2 = divNearest(-b1 * k, n);
|
|
43048
|
+
let k1 = k - c1 * a1 - c2 * a2;
|
|
43049
|
+
let k2 = -c1 * b1 - c2 * b2;
|
|
43050
|
+
const k1neg = k1 < _0n6;
|
|
43051
|
+
const k2neg = k2 < _0n6;
|
|
43052
|
+
if (k1neg)
|
|
43053
|
+
k1 = -k1;
|
|
43054
|
+
if (k2neg)
|
|
43055
|
+
k2 = -k2;
|
|
43056
|
+
const MAX_NUM = bitMask(Math.ceil(bitLen(n) / 2)) + _1n6;
|
|
43057
|
+
if (k1 < _0n6 || k1 >= MAX_NUM || k2 < _0n6 || k2 >= MAX_NUM) {
|
|
43058
|
+
throw new Error("splitScalar (endomorphism): failed, k=" + k);
|
|
43059
|
+
}
|
|
43060
|
+
return { k1neg, k1, k2neg, k2 };
|
|
43061
|
+
}
|
|
43062
|
+
function validateSigFormat(format) {
|
|
43063
|
+
if (!["compact", "recovered", "der"].includes(format))
|
|
43064
|
+
throw new Error('Signature format must be "compact", "recovered", or "der"');
|
|
43065
|
+
return format;
|
|
43066
|
+
}
|
|
43067
|
+
function validateSigOpts(opts, def) {
|
|
43068
|
+
const optsn = {};
|
|
43069
|
+
for (let optName of Object.keys(def)) {
|
|
43070
|
+
optsn[optName] = opts[optName] === void 0 ? def[optName] : opts[optName];
|
|
43071
|
+
}
|
|
43072
|
+
_abool2(optsn.lowS, "lowS");
|
|
43073
|
+
_abool2(optsn.prehash, "prehash");
|
|
43074
|
+
if (optsn.format !== void 0)
|
|
43075
|
+
validateSigFormat(optsn.format);
|
|
43076
|
+
return optsn;
|
|
43077
|
+
}
|
|
43078
|
+
var DERErr = class extends Error {
|
|
43079
|
+
constructor(m = "") {
|
|
43080
|
+
super(m);
|
|
43081
|
+
}
|
|
43082
|
+
};
|
|
43083
|
+
var DER = {
|
|
43084
|
+
// asn.1 DER encoding utils
|
|
43085
|
+
Err: DERErr,
|
|
43086
|
+
// Basic building block is TLV (Tag-Length-Value)
|
|
43087
|
+
_tlv: {
|
|
43088
|
+
encode: (tag, data) => {
|
|
43089
|
+
const { Err: E } = DER;
|
|
43090
|
+
if (tag < 0 || tag > 256)
|
|
43091
|
+
throw new E("tlv.encode: wrong tag");
|
|
43092
|
+
if (data.length & 1)
|
|
43093
|
+
throw new E("tlv.encode: unpadded data");
|
|
43094
|
+
const dataLen = data.length / 2;
|
|
43095
|
+
const len = numberToHexUnpadded(dataLen);
|
|
43096
|
+
if (len.length / 2 & 128)
|
|
43097
|
+
throw new E("tlv.encode: long form length too big");
|
|
43098
|
+
const lenLen = dataLen > 127 ? numberToHexUnpadded(len.length / 2 | 128) : "";
|
|
43099
|
+
const t = numberToHexUnpadded(tag);
|
|
43100
|
+
return t + lenLen + len + data;
|
|
43101
|
+
},
|
|
43102
|
+
// v - value, l - left bytes (unparsed)
|
|
43103
|
+
decode(tag, data) {
|
|
43104
|
+
const { Err: E } = DER;
|
|
43105
|
+
let pos = 0;
|
|
43106
|
+
if (tag < 0 || tag > 256)
|
|
43107
|
+
throw new E("tlv.encode: wrong tag");
|
|
43108
|
+
if (data.length < 2 || data[pos++] !== tag)
|
|
43109
|
+
throw new E("tlv.decode: wrong tlv");
|
|
43110
|
+
const first = data[pos++];
|
|
43111
|
+
const isLong = !!(first & 128);
|
|
43112
|
+
let length = 0;
|
|
43113
|
+
if (!isLong)
|
|
43114
|
+
length = first;
|
|
43115
|
+
else {
|
|
43116
|
+
const lenLen = first & 127;
|
|
43117
|
+
if (!lenLen)
|
|
43118
|
+
throw new E("tlv.decode(long): indefinite length not supported");
|
|
43119
|
+
if (lenLen > 4)
|
|
43120
|
+
throw new E("tlv.decode(long): byte length is too big");
|
|
43121
|
+
const lengthBytes = data.subarray(pos, pos + lenLen);
|
|
43122
|
+
if (lengthBytes.length !== lenLen)
|
|
43123
|
+
throw new E("tlv.decode: length bytes not complete");
|
|
43124
|
+
if (lengthBytes[0] === 0)
|
|
43125
|
+
throw new E("tlv.decode(long): zero leftmost byte");
|
|
43126
|
+
for (const b of lengthBytes)
|
|
43127
|
+
length = length << 8 | b;
|
|
43128
|
+
pos += lenLen;
|
|
43129
|
+
if (length < 128)
|
|
43130
|
+
throw new E("tlv.decode(long): not minimal encoding");
|
|
43131
|
+
}
|
|
43132
|
+
const v = data.subarray(pos, pos + length);
|
|
43133
|
+
if (v.length !== length)
|
|
43134
|
+
throw new E("tlv.decode: wrong value length");
|
|
43135
|
+
return { v, l: data.subarray(pos + length) };
|
|
43136
|
+
}
|
|
43137
|
+
},
|
|
43138
|
+
// https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag,
|
|
43139
|
+
// since we always use positive integers here. It must always be empty:
|
|
43140
|
+
// - add zero byte if exists
|
|
43141
|
+
// - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding)
|
|
43142
|
+
_int: {
|
|
43143
|
+
encode(num) {
|
|
43144
|
+
const { Err: E } = DER;
|
|
43145
|
+
if (num < _0n6)
|
|
43146
|
+
throw new E("integer: negative integers are not allowed");
|
|
43147
|
+
let hex = numberToHexUnpadded(num);
|
|
43148
|
+
if (Number.parseInt(hex[0], 16) & 8)
|
|
43149
|
+
hex = "00" + hex;
|
|
43150
|
+
if (hex.length & 1)
|
|
43151
|
+
throw new E("unexpected DER parsing assertion: unpadded hex");
|
|
43152
|
+
return hex;
|
|
43153
|
+
},
|
|
43154
|
+
decode(data) {
|
|
43155
|
+
const { Err: E } = DER;
|
|
43156
|
+
if (data[0] & 128)
|
|
43157
|
+
throw new E("invalid signature integer: negative");
|
|
43158
|
+
if (data[0] === 0 && !(data[1] & 128))
|
|
43159
|
+
throw new E("invalid signature integer: unnecessary leading zero");
|
|
43160
|
+
return bytesToNumberBE(data);
|
|
43161
|
+
}
|
|
43162
|
+
},
|
|
43163
|
+
toSig(hex) {
|
|
43164
|
+
const { Err: E, _int: int, _tlv: tlv } = DER;
|
|
43165
|
+
const data = ensureBytes("signature", hex);
|
|
43166
|
+
const { v: seqBytes, l: seqLeftBytes } = tlv.decode(48, data);
|
|
43167
|
+
if (seqLeftBytes.length)
|
|
43168
|
+
throw new E("invalid signature: left bytes after parsing");
|
|
43169
|
+
const { v: rBytes, l: rLeftBytes } = tlv.decode(2, seqBytes);
|
|
43170
|
+
const { v: sBytes, l: sLeftBytes } = tlv.decode(2, rLeftBytes);
|
|
43171
|
+
if (sLeftBytes.length)
|
|
43172
|
+
throw new E("invalid signature: left bytes after parsing");
|
|
43173
|
+
return { r: int.decode(rBytes), s: int.decode(sBytes) };
|
|
43174
|
+
},
|
|
43175
|
+
hexFromSig(sig) {
|
|
43176
|
+
const { _tlv: tlv, _int: int } = DER;
|
|
43177
|
+
const rs = tlv.encode(2, int.encode(sig.r));
|
|
43178
|
+
const ss = tlv.encode(2, int.encode(sig.s));
|
|
43179
|
+
const seq = rs + ss;
|
|
43180
|
+
return tlv.encode(48, seq);
|
|
43181
|
+
}
|
|
43182
|
+
};
|
|
43183
|
+
var _0n6 = BigInt(0);
|
|
43184
|
+
var _1n6 = BigInt(1);
|
|
43185
|
+
var _2n4 = BigInt(2);
|
|
43186
|
+
var _3n3 = BigInt(3);
|
|
43187
|
+
var _4n2 = BigInt(4);
|
|
43188
|
+
function _normFnElement(Fn2, key) {
|
|
43189
|
+
const { BYTES: expected } = Fn2;
|
|
43190
|
+
let num;
|
|
43191
|
+
if (typeof key === "bigint") {
|
|
43192
|
+
num = key;
|
|
43193
|
+
} else {
|
|
43194
|
+
let bytes = ensureBytes("private key", key);
|
|
43195
|
+
try {
|
|
43196
|
+
num = Fn2.fromBytes(bytes);
|
|
43197
|
+
} catch (error) {
|
|
43198
|
+
throw new Error(`invalid private key: expected ui8a of size ${expected}, got ${typeof key}`);
|
|
43199
|
+
}
|
|
43200
|
+
}
|
|
43201
|
+
if (!Fn2.isValidNot0(num))
|
|
43202
|
+
throw new Error("invalid private key: out of range [1..N-1]");
|
|
43203
|
+
return num;
|
|
43204
|
+
}
|
|
43205
|
+
function weierstrassN(params, extraOpts = {}) {
|
|
43206
|
+
const validated = _createCurveFields("weierstrass", params, extraOpts);
|
|
43207
|
+
const { Fp: Fp2, Fn: Fn2 } = validated;
|
|
43208
|
+
let CURVE = validated.CURVE;
|
|
43209
|
+
const { h: cofactor, n: CURVE_ORDER } = CURVE;
|
|
43210
|
+
_validateObject(extraOpts, {}, {
|
|
43211
|
+
allowInfinityPoint: "boolean",
|
|
43212
|
+
clearCofactor: "function",
|
|
43213
|
+
isTorsionFree: "function",
|
|
43214
|
+
fromBytes: "function",
|
|
43215
|
+
toBytes: "function",
|
|
43216
|
+
endo: "object",
|
|
43217
|
+
wrapPrivateKey: "boolean"
|
|
43218
|
+
});
|
|
43219
|
+
const { endo } = extraOpts;
|
|
43220
|
+
if (endo) {
|
|
43221
|
+
if (!Fp2.is0(CURVE.a) || typeof endo.beta !== "bigint" || !Array.isArray(endo.basises)) {
|
|
43222
|
+
throw new Error('invalid endo: expected "beta": bigint and "basises": array');
|
|
43223
|
+
}
|
|
43224
|
+
}
|
|
43225
|
+
const lengths = getWLengths(Fp2, Fn2);
|
|
43226
|
+
function assertCompressionIsSupported() {
|
|
43227
|
+
if (!Fp2.isOdd)
|
|
43228
|
+
throw new Error("compression is not supported: Field does not have .isOdd()");
|
|
43229
|
+
}
|
|
43230
|
+
function pointToBytes(_c, point, isCompressed) {
|
|
43231
|
+
const { x, y } = point.toAffine();
|
|
43232
|
+
const bx = Fp2.toBytes(x);
|
|
43233
|
+
_abool2(isCompressed, "isCompressed");
|
|
43234
|
+
if (isCompressed) {
|
|
43235
|
+
assertCompressionIsSupported();
|
|
43236
|
+
const hasEvenY = !Fp2.isOdd(y);
|
|
43237
|
+
return concatBytes(pprefix(hasEvenY), bx);
|
|
43238
|
+
} else {
|
|
43239
|
+
return concatBytes(Uint8Array.of(4), bx, Fp2.toBytes(y));
|
|
43240
|
+
}
|
|
43241
|
+
}
|
|
43242
|
+
function pointFromBytes(bytes) {
|
|
43243
|
+
_abytes2(bytes, void 0, "Point");
|
|
43244
|
+
const { publicKey: comp, publicKeyUncompressed: uncomp } = lengths;
|
|
43245
|
+
const length = bytes.length;
|
|
43246
|
+
const head = bytes[0];
|
|
43247
|
+
const tail = bytes.subarray(1);
|
|
43248
|
+
if (length === comp && (head === 2 || head === 3)) {
|
|
43249
|
+
const x = Fp2.fromBytes(tail);
|
|
43250
|
+
if (!Fp2.isValid(x))
|
|
43251
|
+
throw new Error("bad point: is not on curve, wrong x");
|
|
43252
|
+
const y2 = weierstrassEquation(x);
|
|
43253
|
+
let y;
|
|
43254
|
+
try {
|
|
43255
|
+
y = Fp2.sqrt(y2);
|
|
43256
|
+
} catch (sqrtError) {
|
|
43257
|
+
const err = sqrtError instanceof Error ? ": " + sqrtError.message : "";
|
|
43258
|
+
throw new Error("bad point: is not on curve, sqrt error" + err);
|
|
43259
|
+
}
|
|
43260
|
+
assertCompressionIsSupported();
|
|
43261
|
+
const isYOdd = Fp2.isOdd(y);
|
|
43262
|
+
const isHeadOdd = (head & 1) === 1;
|
|
43263
|
+
if (isHeadOdd !== isYOdd)
|
|
43264
|
+
y = Fp2.neg(y);
|
|
43265
|
+
return { x, y };
|
|
43266
|
+
} else if (length === uncomp && head === 4) {
|
|
43267
|
+
const L = Fp2.BYTES;
|
|
43268
|
+
const x = Fp2.fromBytes(tail.subarray(0, L));
|
|
43269
|
+
const y = Fp2.fromBytes(tail.subarray(L, L * 2));
|
|
43270
|
+
if (!isValidXY(x, y))
|
|
43271
|
+
throw new Error("bad point: is not on curve");
|
|
43272
|
+
return { x, y };
|
|
43273
|
+
} else {
|
|
43274
|
+
throw new Error(`bad point: got length ${length}, expected compressed=${comp} or uncompressed=${uncomp}`);
|
|
43275
|
+
}
|
|
43276
|
+
}
|
|
43277
|
+
const encodePoint = extraOpts.toBytes || pointToBytes;
|
|
43278
|
+
const decodePoint = extraOpts.fromBytes || pointFromBytes;
|
|
43279
|
+
function weierstrassEquation(x) {
|
|
43280
|
+
const x2 = Fp2.sqr(x);
|
|
43281
|
+
const x3 = Fp2.mul(x2, x);
|
|
43282
|
+
return Fp2.add(Fp2.add(x3, Fp2.mul(x, CURVE.a)), CURVE.b);
|
|
43283
|
+
}
|
|
43284
|
+
function isValidXY(x, y) {
|
|
43285
|
+
const left = Fp2.sqr(y);
|
|
43286
|
+
const right = weierstrassEquation(x);
|
|
43287
|
+
return Fp2.eql(left, right);
|
|
43288
|
+
}
|
|
43289
|
+
if (!isValidXY(CURVE.Gx, CURVE.Gy))
|
|
43290
|
+
throw new Error("bad curve params: generator point");
|
|
43291
|
+
const _4a3 = Fp2.mul(Fp2.pow(CURVE.a, _3n3), _4n2);
|
|
43292
|
+
const _27b2 = Fp2.mul(Fp2.sqr(CURVE.b), BigInt(27));
|
|
43293
|
+
if (Fp2.is0(Fp2.add(_4a3, _27b2)))
|
|
43294
|
+
throw new Error("bad curve params: a or b");
|
|
43295
|
+
function acoord(title, n, banZero = false) {
|
|
43296
|
+
if (!Fp2.isValid(n) || banZero && Fp2.is0(n))
|
|
43297
|
+
throw new Error(`bad point coordinate ${title}`);
|
|
43298
|
+
return n;
|
|
43299
|
+
}
|
|
43300
|
+
function aprjpoint(other) {
|
|
43301
|
+
if (!(other instanceof Point))
|
|
43302
|
+
throw new Error("ProjectivePoint expected");
|
|
43303
|
+
}
|
|
43304
|
+
function splitEndoScalarN(k) {
|
|
43305
|
+
if (!endo || !endo.basises)
|
|
43306
|
+
throw new Error("no endo");
|
|
43307
|
+
return _splitEndoScalar(k, endo.basises, Fn2.ORDER);
|
|
43308
|
+
}
|
|
43309
|
+
const toAffineMemo = memoized((p, iz) => {
|
|
43310
|
+
const { X, Y, Z } = p;
|
|
43311
|
+
if (Fp2.eql(Z, Fp2.ONE))
|
|
43312
|
+
return { x: X, y: Y };
|
|
43313
|
+
const is0 = p.is0();
|
|
43314
|
+
if (iz == null)
|
|
43315
|
+
iz = is0 ? Fp2.ONE : Fp2.inv(Z);
|
|
43316
|
+
const x = Fp2.mul(X, iz);
|
|
43317
|
+
const y = Fp2.mul(Y, iz);
|
|
43318
|
+
const zz = Fp2.mul(Z, iz);
|
|
43319
|
+
if (is0)
|
|
43320
|
+
return { x: Fp2.ZERO, y: Fp2.ZERO };
|
|
43321
|
+
if (!Fp2.eql(zz, Fp2.ONE))
|
|
43322
|
+
throw new Error("invZ was invalid");
|
|
43323
|
+
return { x, y };
|
|
43324
|
+
});
|
|
43325
|
+
const assertValidMemo = memoized((p) => {
|
|
43326
|
+
if (p.is0()) {
|
|
43327
|
+
if (extraOpts.allowInfinityPoint && !Fp2.is0(p.Y))
|
|
43328
|
+
return;
|
|
43329
|
+
throw new Error("bad point: ZERO");
|
|
43330
|
+
}
|
|
43331
|
+
const { x, y } = p.toAffine();
|
|
43332
|
+
if (!Fp2.isValid(x) || !Fp2.isValid(y))
|
|
43333
|
+
throw new Error("bad point: x or y not field elements");
|
|
43334
|
+
if (!isValidXY(x, y))
|
|
43335
|
+
throw new Error("bad point: equation left != right");
|
|
43336
|
+
if (!p.isTorsionFree())
|
|
43337
|
+
throw new Error("bad point: not in prime-order subgroup");
|
|
43338
|
+
return true;
|
|
43339
|
+
});
|
|
43340
|
+
function finishEndo(endoBeta, k1p, k2p, k1neg, k2neg) {
|
|
43341
|
+
k2p = new Point(Fp2.mul(k2p.X, endoBeta), k2p.Y, k2p.Z);
|
|
43342
|
+
k1p = negateCt(k1neg, k1p);
|
|
43343
|
+
k2p = negateCt(k2neg, k2p);
|
|
43344
|
+
return k1p.add(k2p);
|
|
43345
|
+
}
|
|
43346
|
+
class Point {
|
|
43347
|
+
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
|
43348
|
+
constructor(X, Y, Z) {
|
|
43349
|
+
this.X = acoord("x", X);
|
|
43350
|
+
this.Y = acoord("y", Y, true);
|
|
43351
|
+
this.Z = acoord("z", Z);
|
|
43352
|
+
Object.freeze(this);
|
|
43353
|
+
}
|
|
43354
|
+
static CURVE() {
|
|
43355
|
+
return CURVE;
|
|
43356
|
+
}
|
|
43357
|
+
/** Does NOT validate if the point is valid. Use `.assertValidity()`. */
|
|
43358
|
+
static fromAffine(p) {
|
|
43359
|
+
const { x, y } = p || {};
|
|
43360
|
+
if (!p || !Fp2.isValid(x) || !Fp2.isValid(y))
|
|
43361
|
+
throw new Error("invalid affine point");
|
|
43362
|
+
if (p instanceof Point)
|
|
43363
|
+
throw new Error("projective point not allowed");
|
|
43364
|
+
if (Fp2.is0(x) && Fp2.is0(y))
|
|
43365
|
+
return Point.ZERO;
|
|
43366
|
+
return new Point(x, y, Fp2.ONE);
|
|
43367
|
+
}
|
|
43368
|
+
static fromBytes(bytes) {
|
|
43369
|
+
const P = Point.fromAffine(decodePoint(_abytes2(bytes, void 0, "point")));
|
|
43370
|
+
P.assertValidity();
|
|
43371
|
+
return P;
|
|
43372
|
+
}
|
|
43373
|
+
static fromHex(hex) {
|
|
43374
|
+
return Point.fromBytes(ensureBytes("pointHex", hex));
|
|
43375
|
+
}
|
|
43376
|
+
get x() {
|
|
43377
|
+
return this.toAffine().x;
|
|
43378
|
+
}
|
|
43379
|
+
get y() {
|
|
43380
|
+
return this.toAffine().y;
|
|
43381
|
+
}
|
|
43382
|
+
/**
|
|
43383
|
+
*
|
|
43384
|
+
* @param windowSize
|
|
43385
|
+
* @param isLazy true will defer table computation until the first multiplication
|
|
43386
|
+
* @returns
|
|
43387
|
+
*/
|
|
43388
|
+
precompute(windowSize = 8, isLazy = true) {
|
|
43389
|
+
wnaf.createCache(this, windowSize);
|
|
43390
|
+
if (!isLazy)
|
|
43391
|
+
this.multiply(_3n3);
|
|
43392
|
+
return this;
|
|
43393
|
+
}
|
|
43394
|
+
// TODO: return `this`
|
|
43395
|
+
/** A point on curve is valid if it conforms to equation. */
|
|
43396
|
+
assertValidity() {
|
|
43397
|
+
assertValidMemo(this);
|
|
43398
|
+
}
|
|
43399
|
+
hasEvenY() {
|
|
43400
|
+
const { y } = this.toAffine();
|
|
43401
|
+
if (!Fp2.isOdd)
|
|
43402
|
+
throw new Error("Field doesn't support isOdd");
|
|
43403
|
+
return !Fp2.isOdd(y);
|
|
43404
|
+
}
|
|
43405
|
+
/** Compare one point to another. */
|
|
43406
|
+
equals(other) {
|
|
43407
|
+
aprjpoint(other);
|
|
43408
|
+
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
43409
|
+
const { X: X2, Y: Y2, Z: Z2 } = other;
|
|
43410
|
+
const U1 = Fp2.eql(Fp2.mul(X1, Z2), Fp2.mul(X2, Z1));
|
|
43411
|
+
const U2 = Fp2.eql(Fp2.mul(Y1, Z2), Fp2.mul(Y2, Z1));
|
|
43412
|
+
return U1 && U2;
|
|
43413
|
+
}
|
|
43414
|
+
/** Flips point to one corresponding to (x, -y) in Affine coordinates. */
|
|
43415
|
+
negate() {
|
|
43416
|
+
return new Point(this.X, Fp2.neg(this.Y), this.Z);
|
|
43417
|
+
}
|
|
43418
|
+
// Renes-Costello-Batina exception-free doubling formula.
|
|
43419
|
+
// There is 30% faster Jacobian formula, but it is not complete.
|
|
43420
|
+
// https://eprint.iacr.org/2015/1060, algorithm 3
|
|
43421
|
+
// Cost: 8M + 3S + 3*a + 2*b3 + 15add.
|
|
43422
|
+
double() {
|
|
43423
|
+
const { a, b } = CURVE;
|
|
43424
|
+
const b3 = Fp2.mul(b, _3n3);
|
|
43425
|
+
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
43426
|
+
let X3 = Fp2.ZERO, Y3 = Fp2.ZERO, Z3 = Fp2.ZERO;
|
|
43427
|
+
let t0 = Fp2.mul(X1, X1);
|
|
43428
|
+
let t1 = Fp2.mul(Y1, Y1);
|
|
43429
|
+
let t2 = Fp2.mul(Z1, Z1);
|
|
43430
|
+
let t3 = Fp2.mul(X1, Y1);
|
|
43431
|
+
t3 = Fp2.add(t3, t3);
|
|
43432
|
+
Z3 = Fp2.mul(X1, Z1);
|
|
43433
|
+
Z3 = Fp2.add(Z3, Z3);
|
|
43434
|
+
X3 = Fp2.mul(a, Z3);
|
|
43435
|
+
Y3 = Fp2.mul(b3, t2);
|
|
43436
|
+
Y3 = Fp2.add(X3, Y3);
|
|
43437
|
+
X3 = Fp2.sub(t1, Y3);
|
|
43438
|
+
Y3 = Fp2.add(t1, Y3);
|
|
43439
|
+
Y3 = Fp2.mul(X3, Y3);
|
|
43440
|
+
X3 = Fp2.mul(t3, X3);
|
|
43441
|
+
Z3 = Fp2.mul(b3, Z3);
|
|
43442
|
+
t2 = Fp2.mul(a, t2);
|
|
43443
|
+
t3 = Fp2.sub(t0, t2);
|
|
43444
|
+
t3 = Fp2.mul(a, t3);
|
|
43445
|
+
t3 = Fp2.add(t3, Z3);
|
|
43446
|
+
Z3 = Fp2.add(t0, t0);
|
|
43447
|
+
t0 = Fp2.add(Z3, t0);
|
|
43448
|
+
t0 = Fp2.add(t0, t2);
|
|
43449
|
+
t0 = Fp2.mul(t0, t3);
|
|
43450
|
+
Y3 = Fp2.add(Y3, t0);
|
|
43451
|
+
t2 = Fp2.mul(Y1, Z1);
|
|
43452
|
+
t2 = Fp2.add(t2, t2);
|
|
43453
|
+
t0 = Fp2.mul(t2, t3);
|
|
43454
|
+
X3 = Fp2.sub(X3, t0);
|
|
43455
|
+
Z3 = Fp2.mul(t2, t1);
|
|
43456
|
+
Z3 = Fp2.add(Z3, Z3);
|
|
43457
|
+
Z3 = Fp2.add(Z3, Z3);
|
|
43458
|
+
return new Point(X3, Y3, Z3);
|
|
43459
|
+
}
|
|
43460
|
+
// Renes-Costello-Batina exception-free addition formula.
|
|
43461
|
+
// There is 30% faster Jacobian formula, but it is not complete.
|
|
43462
|
+
// https://eprint.iacr.org/2015/1060, algorithm 1
|
|
43463
|
+
// Cost: 12M + 0S + 3*a + 3*b3 + 23add.
|
|
43464
|
+
add(other) {
|
|
43465
|
+
aprjpoint(other);
|
|
43466
|
+
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
43467
|
+
const { X: X2, Y: Y2, Z: Z2 } = other;
|
|
43468
|
+
let X3 = Fp2.ZERO, Y3 = Fp2.ZERO, Z3 = Fp2.ZERO;
|
|
43469
|
+
const a = CURVE.a;
|
|
43470
|
+
const b3 = Fp2.mul(CURVE.b, _3n3);
|
|
43471
|
+
let t0 = Fp2.mul(X1, X2);
|
|
43472
|
+
let t1 = Fp2.mul(Y1, Y2);
|
|
43473
|
+
let t2 = Fp2.mul(Z1, Z2);
|
|
43474
|
+
let t3 = Fp2.add(X1, Y1);
|
|
43475
|
+
let t4 = Fp2.add(X2, Y2);
|
|
43476
|
+
t3 = Fp2.mul(t3, t4);
|
|
43477
|
+
t4 = Fp2.add(t0, t1);
|
|
43478
|
+
t3 = Fp2.sub(t3, t4);
|
|
43479
|
+
t4 = Fp2.add(X1, Z1);
|
|
43480
|
+
let t5 = Fp2.add(X2, Z2);
|
|
43481
|
+
t4 = Fp2.mul(t4, t5);
|
|
43482
|
+
t5 = Fp2.add(t0, t2);
|
|
43483
|
+
t4 = Fp2.sub(t4, t5);
|
|
43484
|
+
t5 = Fp2.add(Y1, Z1);
|
|
43485
|
+
X3 = Fp2.add(Y2, Z2);
|
|
43486
|
+
t5 = Fp2.mul(t5, X3);
|
|
43487
|
+
X3 = Fp2.add(t1, t2);
|
|
43488
|
+
t5 = Fp2.sub(t5, X3);
|
|
43489
|
+
Z3 = Fp2.mul(a, t4);
|
|
43490
|
+
X3 = Fp2.mul(b3, t2);
|
|
43491
|
+
Z3 = Fp2.add(X3, Z3);
|
|
43492
|
+
X3 = Fp2.sub(t1, Z3);
|
|
43493
|
+
Z3 = Fp2.add(t1, Z3);
|
|
43494
|
+
Y3 = Fp2.mul(X3, Z3);
|
|
43495
|
+
t1 = Fp2.add(t0, t0);
|
|
43496
|
+
t1 = Fp2.add(t1, t0);
|
|
43497
|
+
t2 = Fp2.mul(a, t2);
|
|
43498
|
+
t4 = Fp2.mul(b3, t4);
|
|
43499
|
+
t1 = Fp2.add(t1, t2);
|
|
43500
|
+
t2 = Fp2.sub(t0, t2);
|
|
43501
|
+
t2 = Fp2.mul(a, t2);
|
|
43502
|
+
t4 = Fp2.add(t4, t2);
|
|
43503
|
+
t0 = Fp2.mul(t1, t4);
|
|
43504
|
+
Y3 = Fp2.add(Y3, t0);
|
|
43505
|
+
t0 = Fp2.mul(t5, t4);
|
|
43506
|
+
X3 = Fp2.mul(t3, X3);
|
|
43507
|
+
X3 = Fp2.sub(X3, t0);
|
|
43508
|
+
t0 = Fp2.mul(t3, t1);
|
|
43509
|
+
Z3 = Fp2.mul(t5, Z3);
|
|
43510
|
+
Z3 = Fp2.add(Z3, t0);
|
|
43511
|
+
return new Point(X3, Y3, Z3);
|
|
43512
|
+
}
|
|
43513
|
+
subtract(other) {
|
|
43514
|
+
return this.add(other.negate());
|
|
43515
|
+
}
|
|
43516
|
+
is0() {
|
|
43517
|
+
return this.equals(Point.ZERO);
|
|
43518
|
+
}
|
|
43519
|
+
/**
|
|
43520
|
+
* Constant time multiplication.
|
|
43521
|
+
* Uses wNAF method. Windowed method may be 10% faster,
|
|
43522
|
+
* but takes 2x longer to generate and consumes 2x memory.
|
|
43523
|
+
* Uses precomputes when available.
|
|
43524
|
+
* Uses endomorphism for Koblitz curves.
|
|
43525
|
+
* @param scalar by which the point would be multiplied
|
|
43526
|
+
* @returns New point
|
|
43527
|
+
*/
|
|
43528
|
+
multiply(scalar) {
|
|
43529
|
+
const { endo: endo2 } = extraOpts;
|
|
43530
|
+
if (!Fn2.isValidNot0(scalar))
|
|
43531
|
+
throw new Error("invalid scalar: out of range");
|
|
43532
|
+
let point, fake;
|
|
43533
|
+
const mul = (n) => wnaf.cached(this, n, (p) => normalizeZ(Point, p));
|
|
43534
|
+
if (endo2) {
|
|
43535
|
+
const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar);
|
|
43536
|
+
const { p: k1p, f: k1f } = mul(k1);
|
|
43537
|
+
const { p: k2p, f: k2f } = mul(k2);
|
|
43538
|
+
fake = k1f.add(k2f);
|
|
43539
|
+
point = finishEndo(endo2.beta, k1p, k2p, k1neg, k2neg);
|
|
43540
|
+
} else {
|
|
43541
|
+
const { p, f } = mul(scalar);
|
|
43542
|
+
point = p;
|
|
43543
|
+
fake = f;
|
|
43544
|
+
}
|
|
43545
|
+
return normalizeZ(Point, [point, fake])[0];
|
|
43546
|
+
}
|
|
43547
|
+
/**
|
|
43548
|
+
* Non-constant-time multiplication. Uses double-and-add algorithm.
|
|
43549
|
+
* It's faster, but should only be used when you don't care about
|
|
43550
|
+
* an exposed secret key e.g. sig verification, which works over *public* keys.
|
|
43551
|
+
*/
|
|
43552
|
+
multiplyUnsafe(sc) {
|
|
43553
|
+
const { endo: endo2 } = extraOpts;
|
|
43554
|
+
const p = this;
|
|
43555
|
+
if (!Fn2.isValid(sc))
|
|
43556
|
+
throw new Error("invalid scalar: out of range");
|
|
43557
|
+
if (sc === _0n6 || p.is0())
|
|
43558
|
+
return Point.ZERO;
|
|
43559
|
+
if (sc === _1n6)
|
|
43560
|
+
return p;
|
|
43561
|
+
if (wnaf.hasCache(this))
|
|
43562
|
+
return this.multiply(sc);
|
|
43563
|
+
if (endo2) {
|
|
43564
|
+
const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(sc);
|
|
43565
|
+
const { p1, p2 } = mulEndoUnsafe(Point, p, k1, k2);
|
|
43566
|
+
return finishEndo(endo2.beta, p1, p2, k1neg, k2neg);
|
|
43567
|
+
} else {
|
|
43568
|
+
return wnaf.unsafe(p, sc);
|
|
43569
|
+
}
|
|
43570
|
+
}
|
|
43571
|
+
multiplyAndAddUnsafe(Q, a, b) {
|
|
43572
|
+
const sum = this.multiplyUnsafe(a).add(Q.multiplyUnsafe(b));
|
|
43573
|
+
return sum.is0() ? void 0 : sum;
|
|
43574
|
+
}
|
|
43575
|
+
/**
|
|
43576
|
+
* Converts Projective point to affine (x, y) coordinates.
|
|
43577
|
+
* @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch
|
|
43578
|
+
*/
|
|
43579
|
+
toAffine(invertedZ) {
|
|
43580
|
+
return toAffineMemo(this, invertedZ);
|
|
43581
|
+
}
|
|
43582
|
+
/**
|
|
43583
|
+
* Checks whether Point is free of torsion elements (is in prime subgroup).
|
|
43584
|
+
* Always torsion-free for cofactor=1 curves.
|
|
43585
|
+
*/
|
|
43586
|
+
isTorsionFree() {
|
|
43587
|
+
const { isTorsionFree } = extraOpts;
|
|
43588
|
+
if (cofactor === _1n6)
|
|
43589
|
+
return true;
|
|
43590
|
+
if (isTorsionFree)
|
|
43591
|
+
return isTorsionFree(Point, this);
|
|
43592
|
+
return wnaf.unsafe(this, CURVE_ORDER).is0();
|
|
43593
|
+
}
|
|
43594
|
+
clearCofactor() {
|
|
43595
|
+
const { clearCofactor } = extraOpts;
|
|
43596
|
+
if (cofactor === _1n6)
|
|
43597
|
+
return this;
|
|
43598
|
+
if (clearCofactor)
|
|
43599
|
+
return clearCofactor(Point, this);
|
|
43600
|
+
return this.multiplyUnsafe(cofactor);
|
|
43601
|
+
}
|
|
43602
|
+
isSmallOrder() {
|
|
43603
|
+
return this.multiplyUnsafe(cofactor).is0();
|
|
43604
|
+
}
|
|
43605
|
+
toBytes(isCompressed = true) {
|
|
43606
|
+
_abool2(isCompressed, "isCompressed");
|
|
43607
|
+
this.assertValidity();
|
|
43608
|
+
return encodePoint(Point, this, isCompressed);
|
|
43609
|
+
}
|
|
43610
|
+
toHex(isCompressed = true) {
|
|
43611
|
+
return bytesToHex(this.toBytes(isCompressed));
|
|
43612
|
+
}
|
|
43613
|
+
toString() {
|
|
43614
|
+
return `<Point ${this.is0() ? "ZERO" : this.toHex()}>`;
|
|
43615
|
+
}
|
|
43616
|
+
// TODO: remove
|
|
43617
|
+
get px() {
|
|
43618
|
+
return this.X;
|
|
43619
|
+
}
|
|
43620
|
+
get py() {
|
|
43621
|
+
return this.X;
|
|
43622
|
+
}
|
|
43623
|
+
get pz() {
|
|
43624
|
+
return this.Z;
|
|
43625
|
+
}
|
|
43626
|
+
toRawBytes(isCompressed = true) {
|
|
43627
|
+
return this.toBytes(isCompressed);
|
|
43628
|
+
}
|
|
43629
|
+
_setWindowSize(windowSize) {
|
|
43630
|
+
this.precompute(windowSize);
|
|
43631
|
+
}
|
|
43632
|
+
static normalizeZ(points) {
|
|
43633
|
+
return normalizeZ(Point, points);
|
|
43634
|
+
}
|
|
43635
|
+
static msm(points, scalars) {
|
|
43636
|
+
return pippenger(Point, Fn2, points, scalars);
|
|
43637
|
+
}
|
|
43638
|
+
static fromPrivateKey(privateKey) {
|
|
43639
|
+
return Point.BASE.multiply(_normFnElement(Fn2, privateKey));
|
|
43640
|
+
}
|
|
43641
|
+
}
|
|
43642
|
+
Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp2.ONE);
|
|
43643
|
+
Point.ZERO = new Point(Fp2.ZERO, Fp2.ONE, Fp2.ZERO);
|
|
43644
|
+
Point.Fp = Fp2;
|
|
43645
|
+
Point.Fn = Fn2;
|
|
43646
|
+
const bits = Fn2.BITS;
|
|
43647
|
+
const wnaf = new wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits);
|
|
43648
|
+
Point.BASE.precompute(8);
|
|
43649
|
+
return Point;
|
|
43650
|
+
}
|
|
43651
|
+
function pprefix(hasEvenY) {
|
|
43652
|
+
return Uint8Array.of(hasEvenY ? 2 : 3);
|
|
43653
|
+
}
|
|
43654
|
+
function getWLengths(Fp2, Fn2) {
|
|
43655
|
+
return {
|
|
43656
|
+
secretKey: Fn2.BYTES,
|
|
43657
|
+
publicKey: 1 + Fp2.BYTES,
|
|
43658
|
+
publicKeyUncompressed: 1 + 2 * Fp2.BYTES,
|
|
43659
|
+
publicKeyHasPrefix: true,
|
|
43660
|
+
signature: 2 * Fn2.BYTES
|
|
43661
|
+
};
|
|
43662
|
+
}
|
|
43663
|
+
function ecdh(Point, ecdhOpts = {}) {
|
|
43664
|
+
const { Fn: Fn2 } = Point;
|
|
43665
|
+
const randomBytes_ = ecdhOpts.randomBytes || randomBytes2;
|
|
43666
|
+
const lengths = Object.assign(getWLengths(Point.Fp, Fn2), { seed: getMinHashLength(Fn2.ORDER) });
|
|
43667
|
+
function isValidSecretKey(secretKey) {
|
|
43668
|
+
try {
|
|
43669
|
+
return !!_normFnElement(Fn2, secretKey);
|
|
43670
|
+
} catch (error) {
|
|
43671
|
+
return false;
|
|
43672
|
+
}
|
|
43673
|
+
}
|
|
43674
|
+
function isValidPublicKey(publicKey, isCompressed) {
|
|
43675
|
+
const { publicKey: comp, publicKeyUncompressed } = lengths;
|
|
43676
|
+
try {
|
|
43677
|
+
const l = publicKey.length;
|
|
43678
|
+
if (isCompressed === true && l !== comp)
|
|
43679
|
+
return false;
|
|
43680
|
+
if (isCompressed === false && l !== publicKeyUncompressed)
|
|
43681
|
+
return false;
|
|
43682
|
+
return !!Point.fromBytes(publicKey);
|
|
43683
|
+
} catch (error) {
|
|
43684
|
+
return false;
|
|
43685
|
+
}
|
|
43686
|
+
}
|
|
43687
|
+
function randomSecretKey(seed = randomBytes_(lengths.seed)) {
|
|
43688
|
+
return mapHashToField(_abytes2(seed, lengths.seed, "seed"), Fn2.ORDER);
|
|
43689
|
+
}
|
|
43690
|
+
function getPublicKey(secretKey, isCompressed = true) {
|
|
43691
|
+
return Point.BASE.multiply(_normFnElement(Fn2, secretKey)).toBytes(isCompressed);
|
|
43692
|
+
}
|
|
43693
|
+
function keygen(seed) {
|
|
43694
|
+
const secretKey = randomSecretKey(seed);
|
|
43695
|
+
return { secretKey, publicKey: getPublicKey(secretKey) };
|
|
43696
|
+
}
|
|
43697
|
+
function isProbPub(item) {
|
|
43698
|
+
if (typeof item === "bigint")
|
|
43699
|
+
return false;
|
|
43700
|
+
if (item instanceof Point)
|
|
43701
|
+
return true;
|
|
43702
|
+
const { secretKey, publicKey, publicKeyUncompressed } = lengths;
|
|
43703
|
+
if (Fn2.allowedLengths || secretKey === publicKey)
|
|
43704
|
+
return void 0;
|
|
43705
|
+
const l = ensureBytes("key", item).length;
|
|
43706
|
+
return l === publicKey || l === publicKeyUncompressed;
|
|
43707
|
+
}
|
|
43708
|
+
function getSharedSecret(secretKeyA, publicKeyB, isCompressed = true) {
|
|
43709
|
+
if (isProbPub(secretKeyA) === true)
|
|
43710
|
+
throw new Error("first arg must be private key");
|
|
43711
|
+
if (isProbPub(publicKeyB) === false)
|
|
43712
|
+
throw new Error("second arg must be public key");
|
|
43713
|
+
const s = _normFnElement(Fn2, secretKeyA);
|
|
43714
|
+
const b = Point.fromHex(publicKeyB);
|
|
43715
|
+
return b.multiply(s).toBytes(isCompressed);
|
|
43716
|
+
}
|
|
43717
|
+
const utils = {
|
|
43718
|
+
isValidSecretKey,
|
|
43719
|
+
isValidPublicKey,
|
|
43720
|
+
randomSecretKey,
|
|
43721
|
+
// TODO: remove
|
|
43722
|
+
isValidPrivateKey: isValidSecretKey,
|
|
43723
|
+
randomPrivateKey: randomSecretKey,
|
|
43724
|
+
normPrivateKeyToScalar: (key) => _normFnElement(Fn2, key),
|
|
43725
|
+
precompute(windowSize = 8, point = Point.BASE) {
|
|
43726
|
+
return point.precompute(windowSize, false);
|
|
43727
|
+
}
|
|
43728
|
+
};
|
|
43729
|
+
return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths });
|
|
43730
|
+
}
|
|
43731
|
+
function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
43732
|
+
ahash(hash);
|
|
43733
|
+
_validateObject(ecdsaOpts, {}, {
|
|
43734
|
+
hmac: "function",
|
|
43735
|
+
lowS: "boolean",
|
|
43736
|
+
randomBytes: "function",
|
|
43737
|
+
bits2int: "function",
|
|
43738
|
+
bits2int_modN: "function"
|
|
43739
|
+
});
|
|
43740
|
+
const randomBytes6 = ecdsaOpts.randomBytes || randomBytes2;
|
|
43741
|
+
const hmac2 = ecdsaOpts.hmac || ((key, ...msgs) => hmac(hash, key, concatBytes(...msgs)));
|
|
43742
|
+
const { Fp: Fp2, Fn: Fn2 } = Point;
|
|
43743
|
+
const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn2;
|
|
43744
|
+
const { keygen, getPublicKey, getSharedSecret, utils, lengths } = ecdh(Point, ecdsaOpts);
|
|
43745
|
+
const defaultSigOpts = {
|
|
43746
|
+
prehash: false,
|
|
43747
|
+
lowS: typeof ecdsaOpts.lowS === "boolean" ? ecdsaOpts.lowS : false,
|
|
43748
|
+
format: void 0,
|
|
43749
|
+
//'compact' as ECDSASigFormat,
|
|
43750
|
+
extraEntropy: false
|
|
43751
|
+
};
|
|
43752
|
+
const defaultSigOpts_format = "compact";
|
|
43753
|
+
function isBiggerThanHalfOrder(number) {
|
|
43754
|
+
const HALF = CURVE_ORDER >> _1n6;
|
|
43755
|
+
return number > HALF;
|
|
43756
|
+
}
|
|
43757
|
+
function validateRS(title, num) {
|
|
43758
|
+
if (!Fn2.isValidNot0(num))
|
|
43759
|
+
throw new Error(`invalid signature ${title}: out of range 1..Point.Fn.ORDER`);
|
|
43760
|
+
return num;
|
|
43761
|
+
}
|
|
43762
|
+
function validateSigLength(bytes, format) {
|
|
43763
|
+
validateSigFormat(format);
|
|
43764
|
+
const size = lengths.signature;
|
|
43765
|
+
const sizer = format === "compact" ? size : format === "recovered" ? size + 1 : void 0;
|
|
43766
|
+
return _abytes2(bytes, sizer, `${format} signature`);
|
|
43767
|
+
}
|
|
43768
|
+
class Signature {
|
|
43769
|
+
constructor(r, s, recovery) {
|
|
43770
|
+
this.r = validateRS("r", r);
|
|
43771
|
+
this.s = validateRS("s", s);
|
|
43772
|
+
if (recovery != null)
|
|
43773
|
+
this.recovery = recovery;
|
|
43774
|
+
Object.freeze(this);
|
|
43775
|
+
}
|
|
43776
|
+
static fromBytes(bytes, format = defaultSigOpts_format) {
|
|
43777
|
+
validateSigLength(bytes, format);
|
|
43778
|
+
let recid;
|
|
43779
|
+
if (format === "der") {
|
|
43780
|
+
const { r: r2, s: s2 } = DER.toSig(_abytes2(bytes));
|
|
43781
|
+
return new Signature(r2, s2);
|
|
43782
|
+
}
|
|
43783
|
+
if (format === "recovered") {
|
|
43784
|
+
recid = bytes[0];
|
|
43785
|
+
format = "compact";
|
|
43786
|
+
bytes = bytes.subarray(1);
|
|
43787
|
+
}
|
|
43788
|
+
const L = Fn2.BYTES;
|
|
43789
|
+
const r = bytes.subarray(0, L);
|
|
43790
|
+
const s = bytes.subarray(L, L * 2);
|
|
43791
|
+
return new Signature(Fn2.fromBytes(r), Fn2.fromBytes(s), recid);
|
|
43792
|
+
}
|
|
43793
|
+
static fromHex(hex, format) {
|
|
43794
|
+
return this.fromBytes(hexToBytes(hex), format);
|
|
43795
|
+
}
|
|
43796
|
+
addRecoveryBit(recovery) {
|
|
43797
|
+
return new Signature(this.r, this.s, recovery);
|
|
43798
|
+
}
|
|
43799
|
+
recoverPublicKey(messageHash) {
|
|
43800
|
+
const FIELD_ORDER = Fp2.ORDER;
|
|
43801
|
+
const { r, s, recovery: rec } = this;
|
|
43802
|
+
if (rec == null || ![0, 1, 2, 3].includes(rec))
|
|
43803
|
+
throw new Error("recovery id invalid");
|
|
43804
|
+
const hasCofactor = CURVE_ORDER * _2n4 < FIELD_ORDER;
|
|
43805
|
+
if (hasCofactor && rec > 1)
|
|
43806
|
+
throw new Error("recovery id is ambiguous for h>1 curve");
|
|
43807
|
+
const radj = rec === 2 || rec === 3 ? r + CURVE_ORDER : r;
|
|
43808
|
+
if (!Fp2.isValid(radj))
|
|
43809
|
+
throw new Error("recovery id 2 or 3 invalid");
|
|
43810
|
+
const x = Fp2.toBytes(radj);
|
|
43811
|
+
const R = Point.fromBytes(concatBytes(pprefix((rec & 1) === 0), x));
|
|
43812
|
+
const ir = Fn2.inv(radj);
|
|
43813
|
+
const h = bits2int_modN(ensureBytes("msgHash", messageHash));
|
|
43814
|
+
const u1 = Fn2.create(-h * ir);
|
|
43815
|
+
const u2 = Fn2.create(s * ir);
|
|
43816
|
+
const Q = Point.BASE.multiplyUnsafe(u1).add(R.multiplyUnsafe(u2));
|
|
43817
|
+
if (Q.is0())
|
|
43818
|
+
throw new Error("point at infinify");
|
|
43819
|
+
Q.assertValidity();
|
|
43820
|
+
return Q;
|
|
43821
|
+
}
|
|
43822
|
+
// Signatures should be low-s, to prevent malleability.
|
|
43823
|
+
hasHighS() {
|
|
43824
|
+
return isBiggerThanHalfOrder(this.s);
|
|
43825
|
+
}
|
|
43826
|
+
toBytes(format = defaultSigOpts_format) {
|
|
43827
|
+
validateSigFormat(format);
|
|
43828
|
+
if (format === "der")
|
|
43829
|
+
return hexToBytes(DER.hexFromSig(this));
|
|
43830
|
+
const r = Fn2.toBytes(this.r);
|
|
43831
|
+
const s = Fn2.toBytes(this.s);
|
|
43832
|
+
if (format === "recovered") {
|
|
43833
|
+
if (this.recovery == null)
|
|
43834
|
+
throw new Error("recovery bit must be present");
|
|
43835
|
+
return concatBytes(Uint8Array.of(this.recovery), r, s);
|
|
43836
|
+
}
|
|
43837
|
+
return concatBytes(r, s);
|
|
43838
|
+
}
|
|
43839
|
+
toHex(format) {
|
|
43840
|
+
return bytesToHex(this.toBytes(format));
|
|
43841
|
+
}
|
|
43842
|
+
// TODO: remove
|
|
43843
|
+
assertValidity() {
|
|
43844
|
+
}
|
|
43845
|
+
static fromCompact(hex) {
|
|
43846
|
+
return Signature.fromBytes(ensureBytes("sig", hex), "compact");
|
|
43847
|
+
}
|
|
43848
|
+
static fromDER(hex) {
|
|
43849
|
+
return Signature.fromBytes(ensureBytes("sig", hex), "der");
|
|
43850
|
+
}
|
|
43851
|
+
normalizeS() {
|
|
43852
|
+
return this.hasHighS() ? new Signature(this.r, Fn2.neg(this.s), this.recovery) : this;
|
|
43853
|
+
}
|
|
43854
|
+
toDERRawBytes() {
|
|
43855
|
+
return this.toBytes("der");
|
|
43856
|
+
}
|
|
43857
|
+
toDERHex() {
|
|
43858
|
+
return bytesToHex(this.toBytes("der"));
|
|
43859
|
+
}
|
|
43860
|
+
toCompactRawBytes() {
|
|
43861
|
+
return this.toBytes("compact");
|
|
43862
|
+
}
|
|
43863
|
+
toCompactHex() {
|
|
43864
|
+
return bytesToHex(this.toBytes("compact"));
|
|
43865
|
+
}
|
|
43866
|
+
}
|
|
43867
|
+
const bits2int = ecdsaOpts.bits2int || function bits2int_def(bytes) {
|
|
43868
|
+
if (bytes.length > 8192)
|
|
43869
|
+
throw new Error("input is too large");
|
|
43870
|
+
const num = bytesToNumberBE(bytes);
|
|
43871
|
+
const delta = bytes.length * 8 - fnBits;
|
|
43872
|
+
return delta > 0 ? num >> BigInt(delta) : num;
|
|
43873
|
+
};
|
|
43874
|
+
const bits2int_modN = ecdsaOpts.bits2int_modN || function bits2int_modN_def(bytes) {
|
|
43875
|
+
return Fn2.create(bits2int(bytes));
|
|
43876
|
+
};
|
|
43877
|
+
const ORDER_MASK = bitMask(fnBits);
|
|
43878
|
+
function int2octets(num) {
|
|
43879
|
+
aInRange("num < 2^" + fnBits, num, _0n6, ORDER_MASK);
|
|
43880
|
+
return Fn2.toBytes(num);
|
|
43881
|
+
}
|
|
43882
|
+
function validateMsgAndHash(message, prehash) {
|
|
43883
|
+
_abytes2(message, void 0, "message");
|
|
43884
|
+
return prehash ? _abytes2(hash(message), void 0, "prehashed message") : message;
|
|
43885
|
+
}
|
|
43886
|
+
function prepSig(message, privateKey, opts) {
|
|
43887
|
+
if (["recovered", "canonical"].some((k) => k in opts))
|
|
43888
|
+
throw new Error("sign() legacy options not supported");
|
|
43889
|
+
const { lowS, prehash, extraEntropy } = validateSigOpts(opts, defaultSigOpts);
|
|
43890
|
+
message = validateMsgAndHash(message, prehash);
|
|
43891
|
+
const h1int = bits2int_modN(message);
|
|
43892
|
+
const d = _normFnElement(Fn2, privateKey);
|
|
43893
|
+
const seedArgs = [int2octets(d), int2octets(h1int)];
|
|
43894
|
+
if (extraEntropy != null && extraEntropy !== false) {
|
|
43895
|
+
const e = extraEntropy === true ? randomBytes6(lengths.secretKey) : extraEntropy;
|
|
43896
|
+
seedArgs.push(ensureBytes("extraEntropy", e));
|
|
43897
|
+
}
|
|
43898
|
+
const seed = concatBytes(...seedArgs);
|
|
43899
|
+
const m = h1int;
|
|
43900
|
+
function k2sig(kBytes) {
|
|
43901
|
+
const k = bits2int(kBytes);
|
|
43902
|
+
if (!Fn2.isValidNot0(k))
|
|
43903
|
+
return;
|
|
43904
|
+
const ik = Fn2.inv(k);
|
|
43905
|
+
const q = Point.BASE.multiply(k).toAffine();
|
|
43906
|
+
const r = Fn2.create(q.x);
|
|
43907
|
+
if (r === _0n6)
|
|
43908
|
+
return;
|
|
43909
|
+
const s = Fn2.create(ik * Fn2.create(m + r * d));
|
|
43910
|
+
if (s === _0n6)
|
|
43911
|
+
return;
|
|
43912
|
+
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n6);
|
|
43913
|
+
let normS = s;
|
|
43914
|
+
if (lowS && isBiggerThanHalfOrder(s)) {
|
|
43915
|
+
normS = Fn2.neg(s);
|
|
43916
|
+
recovery ^= 1;
|
|
43917
|
+
}
|
|
43918
|
+
return new Signature(r, normS, recovery);
|
|
43919
|
+
}
|
|
43920
|
+
return { seed, k2sig };
|
|
43921
|
+
}
|
|
43922
|
+
function sign(message, secretKey, opts = {}) {
|
|
43923
|
+
message = ensureBytes("message", message);
|
|
43924
|
+
const { seed, k2sig } = prepSig(message, secretKey, opts);
|
|
43925
|
+
const drbg = createHmacDrbg(hash.outputLen, Fn2.BYTES, hmac2);
|
|
43926
|
+
const sig = drbg(seed, k2sig);
|
|
43927
|
+
return sig;
|
|
43928
|
+
}
|
|
43929
|
+
function tryParsingSig(sg) {
|
|
43930
|
+
let sig = void 0;
|
|
43931
|
+
const isHex = typeof sg === "string" || isBytes(sg);
|
|
43932
|
+
const isObj = !isHex && sg !== null && typeof sg === "object" && typeof sg.r === "bigint" && typeof sg.s === "bigint";
|
|
43933
|
+
if (!isHex && !isObj)
|
|
43934
|
+
throw new Error("invalid signature, expected Uint8Array, hex string or Signature instance");
|
|
43935
|
+
if (isObj) {
|
|
43936
|
+
sig = new Signature(sg.r, sg.s);
|
|
43937
|
+
} else if (isHex) {
|
|
43938
|
+
try {
|
|
43939
|
+
sig = Signature.fromBytes(ensureBytes("sig", sg), "der");
|
|
43940
|
+
} catch (derError) {
|
|
43941
|
+
if (!(derError instanceof DER.Err))
|
|
43942
|
+
throw derError;
|
|
43943
|
+
}
|
|
43944
|
+
if (!sig) {
|
|
43945
|
+
try {
|
|
43946
|
+
sig = Signature.fromBytes(ensureBytes("sig", sg), "compact");
|
|
43947
|
+
} catch (error) {
|
|
43948
|
+
return false;
|
|
43949
|
+
}
|
|
43950
|
+
}
|
|
43951
|
+
}
|
|
43952
|
+
if (!sig)
|
|
43953
|
+
return false;
|
|
43954
|
+
return sig;
|
|
43955
|
+
}
|
|
43956
|
+
function verify(signature, message, publicKey, opts = {}) {
|
|
43957
|
+
const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts);
|
|
43958
|
+
publicKey = ensureBytes("publicKey", publicKey);
|
|
43959
|
+
message = validateMsgAndHash(ensureBytes("message", message), prehash);
|
|
43960
|
+
if ("strict" in opts)
|
|
43961
|
+
throw new Error("options.strict was renamed to lowS");
|
|
43962
|
+
const sig = format === void 0 ? tryParsingSig(signature) : Signature.fromBytes(ensureBytes("sig", signature), format);
|
|
43963
|
+
if (sig === false)
|
|
43964
|
+
return false;
|
|
43965
|
+
try {
|
|
43966
|
+
const P = Point.fromBytes(publicKey);
|
|
43967
|
+
if (lowS && sig.hasHighS())
|
|
43968
|
+
return false;
|
|
43969
|
+
const { r, s } = sig;
|
|
43970
|
+
const h = bits2int_modN(message);
|
|
43971
|
+
const is = Fn2.inv(s);
|
|
43972
|
+
const u1 = Fn2.create(h * is);
|
|
43973
|
+
const u2 = Fn2.create(r * is);
|
|
43974
|
+
const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2));
|
|
43975
|
+
if (R.is0())
|
|
43976
|
+
return false;
|
|
43977
|
+
const v = Fn2.create(R.x);
|
|
43978
|
+
return v === r;
|
|
43979
|
+
} catch (e) {
|
|
43980
|
+
return false;
|
|
43981
|
+
}
|
|
43982
|
+
}
|
|
43983
|
+
function recoverPublicKey(signature, message, opts = {}) {
|
|
43984
|
+
const { prehash } = validateSigOpts(opts, defaultSigOpts);
|
|
43985
|
+
message = validateMsgAndHash(message, prehash);
|
|
43986
|
+
return Signature.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
|
|
43987
|
+
}
|
|
43988
|
+
return Object.freeze({
|
|
43989
|
+
keygen,
|
|
43990
|
+
getPublicKey,
|
|
43991
|
+
getSharedSecret,
|
|
43992
|
+
utils,
|
|
43993
|
+
lengths,
|
|
43994
|
+
Point,
|
|
43995
|
+
sign,
|
|
43996
|
+
verify,
|
|
43997
|
+
recoverPublicKey,
|
|
43998
|
+
Signature,
|
|
43999
|
+
hash
|
|
44000
|
+
});
|
|
44001
|
+
}
|
|
44002
|
+
function _weierstrass_legacy_opts_to_new(c) {
|
|
44003
|
+
const CURVE = {
|
|
44004
|
+
a: c.a,
|
|
44005
|
+
b: c.b,
|
|
44006
|
+
p: c.Fp.ORDER,
|
|
44007
|
+
n: c.n,
|
|
44008
|
+
h: c.h,
|
|
44009
|
+
Gx: c.Gx,
|
|
44010
|
+
Gy: c.Gy
|
|
44011
|
+
};
|
|
44012
|
+
const Fp2 = c.Fp;
|
|
44013
|
+
let allowedLengths = c.allowedPrivateKeyLengths ? Array.from(new Set(c.allowedPrivateKeyLengths.map((l) => Math.ceil(l / 2)))) : void 0;
|
|
44014
|
+
const Fn2 = Field(CURVE.n, {
|
|
44015
|
+
BITS: c.nBitLength,
|
|
44016
|
+
allowedLengths,
|
|
44017
|
+
modFromBytes: c.wrapPrivateKey
|
|
44018
|
+
});
|
|
44019
|
+
const curveOpts = {
|
|
44020
|
+
Fp: Fp2,
|
|
44021
|
+
Fn: Fn2,
|
|
44022
|
+
allowInfinityPoint: c.allowInfinityPoint,
|
|
44023
|
+
endo: c.endo,
|
|
44024
|
+
isTorsionFree: c.isTorsionFree,
|
|
44025
|
+
clearCofactor: c.clearCofactor,
|
|
44026
|
+
fromBytes: c.fromBytes,
|
|
44027
|
+
toBytes: c.toBytes
|
|
44028
|
+
};
|
|
44029
|
+
return { CURVE, curveOpts };
|
|
44030
|
+
}
|
|
44031
|
+
function _ecdsa_legacy_opts_to_new(c) {
|
|
44032
|
+
const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c);
|
|
44033
|
+
const ecdsaOpts = {
|
|
44034
|
+
hmac: c.hmac,
|
|
44035
|
+
randomBytes: c.randomBytes,
|
|
44036
|
+
lowS: c.lowS,
|
|
44037
|
+
bits2int: c.bits2int,
|
|
44038
|
+
bits2int_modN: c.bits2int_modN
|
|
44039
|
+
};
|
|
44040
|
+
return { CURVE, curveOpts, hash: c.hash, ecdsaOpts };
|
|
44041
|
+
}
|
|
44042
|
+
function _ecdsa_new_output_to_legacy(c, _ecdsa) {
|
|
44043
|
+
const Point = _ecdsa.Point;
|
|
44044
|
+
return Object.assign({}, _ecdsa, {
|
|
44045
|
+
ProjectivePoint: Point,
|
|
44046
|
+
CURVE: Object.assign({}, c, nLength(Point.Fn.ORDER, Point.Fn.BITS))
|
|
44047
|
+
});
|
|
44048
|
+
}
|
|
44049
|
+
function weierstrass(c) {
|
|
44050
|
+
const { CURVE, curveOpts, hash, ecdsaOpts } = _ecdsa_legacy_opts_to_new(c);
|
|
44051
|
+
const Point = weierstrassN(CURVE, curveOpts);
|
|
44052
|
+
const signs = ecdsa(Point, hash, ecdsaOpts);
|
|
44053
|
+
return _ecdsa_new_output_to_legacy(c, signs);
|
|
44054
|
+
}
|
|
44055
|
+
|
|
44056
|
+
// node_modules/@noble/curves/esm/_shortw_utils.js
|
|
44057
|
+
function createCurve(curveDef, defHash) {
|
|
44058
|
+
const create = (hash) => weierstrass({ ...curveDef, hash });
|
|
44059
|
+
return { ...create(defHash), create };
|
|
44060
|
+
}
|
|
44061
|
+
|
|
44062
|
+
// node_modules/@noble/curves/esm/nist.js
|
|
44063
|
+
var p256_CURVE = {
|
|
44064
|
+
p: BigInt("0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"),
|
|
44065
|
+
n: BigInt("0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"),
|
|
44066
|
+
h: BigInt(1),
|
|
44067
|
+
a: BigInt("0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc"),
|
|
44068
|
+
b: BigInt("0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"),
|
|
44069
|
+
Gx: BigInt("0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"),
|
|
44070
|
+
Gy: BigInt("0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5")
|
|
44071
|
+
};
|
|
44072
|
+
var p384_CURVE = {
|
|
44073
|
+
p: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff"),
|
|
44074
|
+
n: BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973"),
|
|
44075
|
+
h: BigInt(1),
|
|
44076
|
+
a: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc"),
|
|
44077
|
+
b: BigInt("0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef"),
|
|
44078
|
+
Gx: BigInt("0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"),
|
|
44079
|
+
Gy: BigInt("0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f")
|
|
44080
|
+
};
|
|
44081
|
+
var p521_CURVE = {
|
|
44082
|
+
p: BigInt("0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"),
|
|
44083
|
+
n: BigInt("0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409"),
|
|
44084
|
+
h: BigInt(1),
|
|
44085
|
+
a: BigInt("0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc"),
|
|
44086
|
+
b: BigInt("0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00"),
|
|
44087
|
+
Gx: BigInt("0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66"),
|
|
44088
|
+
Gy: BigInt("0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650")
|
|
44089
|
+
};
|
|
44090
|
+
var Fp256 = Field(p256_CURVE.p);
|
|
44091
|
+
var Fp384 = Field(p384_CURVE.p);
|
|
44092
|
+
var Fp521 = Field(p521_CURVE.p);
|
|
44093
|
+
var p256 = createCurve({ ...p256_CURVE, Fp: Fp256, lowS: false }, sha256);
|
|
44094
|
+
var p384 = createCurve({ ...p384_CURVE, Fp: Fp384, lowS: false }, sha384);
|
|
44095
|
+
var p521 = createCurve({ ...p521_CURVE, Fp: Fp521, lowS: false, allowedPrivateKeyLengths: [130, 131, 132] }, sha512);
|
|
44096
|
+
|
|
44097
|
+
// node_modules/@noble/curves/esm/p256.js
|
|
44098
|
+
var p2562 = p256;
|
|
44099
|
+
|
|
44100
|
+
// src/webauthn-approval.ts
|
|
42440
44101
|
function createApprovalChallenge(requestId, toolName, agentId, rpId = "scopeblind.com", timeoutSeconds = 300) {
|
|
42441
44102
|
const challengeBytes = (0, import_node_crypto8.randomBytes)(32);
|
|
42442
44103
|
const contextHash = (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify({ requestId, toolName, agentId, timestamp: Date.now() })).digest("hex");
|
|
@@ -42468,43 +44129,72 @@ function toCredentialRequestOptions(challenge, allowCredentials) {
|
|
|
42468
44129
|
}
|
|
42469
44130
|
};
|
|
42470
44131
|
}
|
|
42471
|
-
function verifyApprovalAssertion(challenge, assertion) {
|
|
44132
|
+
function verifyApprovalAssertion(challenge, assertion, credentialPublicKey, opts = {}) {
|
|
44133
|
+
const now = opts.now ?? Date.now();
|
|
44134
|
+
const fail = (reason, partial = {}) => ({
|
|
44135
|
+
valid: false,
|
|
44136
|
+
reason,
|
|
44137
|
+
credentialId: assertion.credentialId,
|
|
44138
|
+
authenticatorType: "unknown",
|
|
44139
|
+
userVerified: false,
|
|
44140
|
+
signCount: 0,
|
|
44141
|
+
contextHash: challenge.contextHash,
|
|
44142
|
+
approvedAt: new Date(now).toISOString(),
|
|
44143
|
+
...partial
|
|
44144
|
+
});
|
|
42472
44145
|
const createdAt = new Date(challenge.createdAt).getTime();
|
|
42473
|
-
|
|
42474
|
-
if (
|
|
42475
|
-
|
|
42476
|
-
|
|
42477
|
-
|
|
42478
|
-
|
|
42479
|
-
|
|
42480
|
-
|
|
42481
|
-
contextHash: challenge.contextHash,
|
|
42482
|
-
approvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
42483
|
-
};
|
|
44146
|
+
if (now - createdAt > challenge.timeoutSeconds * 1e3) return fail("challenge_expired");
|
|
44147
|
+
if (!credentialPublicKey?.publicKeyHex) return fail("missing_credential_public_key");
|
|
44148
|
+
const clientDataBytes = base64urlDecode2(assertion.clientDataJSON);
|
|
44149
|
+
let clientData;
|
|
44150
|
+
try {
|
|
44151
|
+
clientData = JSON.parse(Buffer.from(clientDataBytes).toString("utf8"));
|
|
44152
|
+
} catch {
|
|
44153
|
+
return fail("client_data_parse_error");
|
|
42484
44154
|
}
|
|
44155
|
+
if (clientData.type !== "webauthn.get") return fail("wrong_client_data_type");
|
|
44156
|
+
if (!constantTimeStrEqual(clientData.challenge ?? "", challenge.challenge)) return fail("challenge_mismatch");
|
|
44157
|
+
const allowedOrigins = opts.expectedOrigin ? Array.isArray(opts.expectedOrigin) ? opts.expectedOrigin : [opts.expectedOrigin] : [`https://${challenge.rpId}`];
|
|
44158
|
+
if (!clientData.origin || !allowedOrigins.includes(clientData.origin)) return fail("origin_mismatch");
|
|
42485
44159
|
const authData = base64urlDecode2(assertion.authenticatorData);
|
|
44160
|
+
if (authData.length < 37) return fail("authenticator_data_too_short");
|
|
44161
|
+
const rpIdHash = authData.slice(0, 32);
|
|
44162
|
+
const expectedRpIdHash = sha2562(new TextEncoder().encode(challenge.rpId));
|
|
44163
|
+
if (!bytesEqual(rpIdHash, expectedRpIdHash)) return fail("rp_id_hash_mismatch");
|
|
42486
44164
|
const flags = authData[32];
|
|
42487
44165
|
const userPresent = !!(flags & 1);
|
|
42488
44166
|
const userVerified = !!(flags & 4);
|
|
42489
|
-
|
|
42490
|
-
|
|
42491
|
-
|
|
44167
|
+
if (!userPresent) return fail("user_not_present");
|
|
44168
|
+
if ((opts.requireUserVerification ?? true) && !userVerified) return fail("user_verification_required", { userVerified });
|
|
44169
|
+
const signCount = authData[33] << 24 | authData[34] << 16 | authData[35] << 8 | authData[36];
|
|
44170
|
+
if (typeof opts.prevSignCount === "number" && signCount !== 0 && signCount <= opts.prevSignCount) {
|
|
44171
|
+
return fail("sign_count_regression", { userVerified, signCount });
|
|
44172
|
+
}
|
|
44173
|
+
const signedData = concatBytes2(authData, sha2562(clientDataBytes));
|
|
44174
|
+
const sigBytes = base64urlDecode2(assertion.signature);
|
|
44175
|
+
let sigOk = false;
|
|
42492
44176
|
try {
|
|
42493
|
-
|
|
42494
|
-
|
|
42495
|
-
|
|
44177
|
+
if (credentialPublicKey.alg === -7) {
|
|
44178
|
+
sigOk = p2562.verify(sigBytes, sha2562(signedData), hexToBytes(credentialPublicKey.publicKeyHex), { format: "der" });
|
|
44179
|
+
} else if (credentialPublicKey.alg === -8) {
|
|
44180
|
+
sigOk = ed25519.verify(sigBytes, signedData, hexToBytes(credentialPublicKey.publicKeyHex));
|
|
44181
|
+
} else {
|
|
44182
|
+
return fail("unsupported_algorithm", { userVerified, signCount });
|
|
42496
44183
|
}
|
|
42497
44184
|
} catch {
|
|
44185
|
+
sigOk = false;
|
|
42498
44186
|
}
|
|
44187
|
+
if (!sigOk) return fail("invalid_signature", { userVerified, signCount });
|
|
42499
44188
|
return {
|
|
42500
|
-
valid:
|
|
42501
|
-
// At minimum, user must be present
|
|
44189
|
+
valid: true,
|
|
42502
44190
|
credentialId: assertion.credentialId,
|
|
42503
|
-
|
|
44191
|
+
// Heuristic: platform authenticators (TouchID/FaceID/Hello) report UV; roaming
|
|
44192
|
+
// keys without a PIN are UP-only. Attachment is authoritative only at registration.
|
|
44193
|
+
authenticatorType: userVerified ? "platform" : "cross-platform",
|
|
42504
44194
|
userVerified,
|
|
42505
44195
|
signCount,
|
|
42506
44196
|
contextHash: challenge.contextHash,
|
|
42507
|
-
approvedAt:
|
|
44197
|
+
approvedAt: new Date(now).toISOString()
|
|
42508
44198
|
};
|
|
42509
44199
|
}
|
|
42510
44200
|
function createApprovalReceiptPayload(challenge, result) {
|
|
@@ -42530,6 +44220,22 @@ function base64urlDecode2(str) {
|
|
|
42530
44220
|
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
42531
44221
|
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
42532
44222
|
}
|
|
44223
|
+
function concatBytes2(a, b) {
|
|
44224
|
+
const out = new Uint8Array(a.length + b.length);
|
|
44225
|
+
out.set(a, 0);
|
|
44226
|
+
out.set(b, a.length);
|
|
44227
|
+
return out;
|
|
44228
|
+
}
|
|
44229
|
+
function bytesEqual(a, b) {
|
|
44230
|
+
if (a.length !== b.length) return false;
|
|
44231
|
+
return (0, import_node_crypto8.timingSafeEqual)(Buffer.from(a), Buffer.from(b));
|
|
44232
|
+
}
|
|
44233
|
+
function constantTimeStrEqual(a, b) {
|
|
44234
|
+
const ab = Buffer.from(a, "utf8");
|
|
44235
|
+
const bb = Buffer.from(b, "utf8");
|
|
44236
|
+
if (ab.length !== bb.length) return false;
|
|
44237
|
+
return (0, import_node_crypto8.timingSafeEqual)(ab, bb);
|
|
44238
|
+
}
|
|
42533
44239
|
|
|
42534
44240
|
// src/did-vc.ts
|
|
42535
44241
|
function ed25519ToDIDKey(publicKeyHex) {
|
|
@@ -43451,7 +45157,7 @@ function handleRequest(request) {
|
|
|
43451
45157
|
id: request.id,
|
|
43452
45158
|
result: {
|
|
43453
45159
|
protocolVersion: "2024-11-05",
|
|
43454
|
-
serverInfo: { name: "protect-mcp-demo", version: "0.5.3" },
|
|
45160
|
+
serverInfo: { name: "protect-mcp-demo", version: process.env.PROTECT_MCP_VERSION || "0.5.3" },
|
|
43455
45161
|
capabilities: { tools: {} }
|
|
43456
45162
|
}
|
|
43457
45163
|
});
|
|
@@ -43537,7 +45243,7 @@ function createSandboxServer() {
|
|
|
43537
45243
|
const { z } = require_zod();
|
|
43538
45244
|
const server = new McpServer({
|
|
43539
45245
|
name: "protect-mcp",
|
|
43540
|
-
version: "0.4.5",
|
|
45246
|
+
version: process.env.PROTECT_MCP_VERSION || "0.4.5",
|
|
43541
45247
|
description: "Security gateway for MCP servers. Per-tool policies, Ed25519-signed receipts, human approval gates, trust tiers."
|
|
43542
45248
|
});
|
|
43543
45249
|
server.tool(
|
|
@@ -43739,5 +45445,9 @@ function createSandboxServer() {
|
|
|
43739
45445
|
@noble/curves/esm/abstract/curve.js:
|
|
43740
45446
|
@noble/curves/esm/abstract/edwards.js:
|
|
43741
45447
|
@noble/curves/esm/ed25519.js:
|
|
45448
|
+
@noble/curves/esm/abstract/weierstrass.js:
|
|
45449
|
+
@noble/curves/esm/_shortw_utils.js:
|
|
45450
|
+
@noble/curves/esm/nist.js:
|
|
45451
|
+
@noble/curves/esm/p256.js:
|
|
43742
45452
|
(*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *)
|
|
43743
45453
|
*/
|