@secondlayer/verify 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/index.d.ts +134 -17
- package/dist/index.js +252 -23
- package/dist/index.js.map +8 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -100,7 +100,34 @@ function parseNakamotoHeader(raw) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
var signerSignatureHash = (h) => sha512_2562(h.signerSighashPreimage);
|
|
103
|
-
var
|
|
103
|
+
var EPOCH2_HEADER_LENGTH = 247;
|
|
104
|
+
function parseEpoch2Header(raw, consensusHash) {
|
|
105
|
+
if (consensusHash.length !== 20)
|
|
106
|
+
throw new Error(`consensus hash is ${consensusHash.length} bytes, not 20`);
|
|
107
|
+
const r = new Reader(raw);
|
|
108
|
+
const header = {
|
|
109
|
+
version: r.u8(),
|
|
110
|
+
burnSpent: r.u64(),
|
|
111
|
+
chainLength: r.u64(),
|
|
112
|
+
vrfProof: r.bytes(80),
|
|
113
|
+
parentBlockHash: r.bytes(32),
|
|
114
|
+
parentMicroblock: r.bytes(32),
|
|
115
|
+
parentMicroblockSequence: r.u16(),
|
|
116
|
+
txMerkleRoot: r.bytes(32),
|
|
117
|
+
stateIndexRoot: r.bytes(32),
|
|
118
|
+
microblockPubkeyHash: r.bytes(20)
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
...header,
|
|
122
|
+
blockHash: sha512_2562(raw.subarray(0, EPOCH2_HEADER_LENGTH)),
|
|
123
|
+
consensusHash
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
var isEpoch2Header = (h) => ("parentBlockHash" in h);
|
|
127
|
+
var blockId = (h) => hashAll([
|
|
128
|
+
isEpoch2Header(h) ? h.blockHash : signerSignatureHash(h),
|
|
129
|
+
h.consensusHash
|
|
130
|
+
]);
|
|
104
131
|
// src/marf.ts
|
|
105
132
|
import { sha512_256 as sha512_2563 } from "@noble/hashes/sha2.js";
|
|
106
133
|
var MARF_VALUE_SIZE = 40;
|
|
@@ -120,6 +147,7 @@ var NODE_PTR_COUNT = {
|
|
|
120
147
|
};
|
|
121
148
|
var isBackptr = (id) => (id & 128) !== 0;
|
|
122
149
|
var marfPath = (key) => sha512_2563(new TextEncoder().encode(key));
|
|
150
|
+
var heightToHashKey = (height) => `__MARF_BLOCK_HEIGHT_TO_HASH::${height}`;
|
|
123
151
|
function marfValue(value) {
|
|
124
152
|
const v = new Uint8Array(MARF_VALUE_SIZE);
|
|
125
153
|
v.set(sha512_2563(new TextEncoder().encode(value)));
|
|
@@ -352,6 +380,14 @@ function marfProofAncestors(proof) {
|
|
|
352
380
|
}
|
|
353
381
|
return [...ids];
|
|
354
382
|
}
|
|
383
|
+
function marfProofValue(proof) {
|
|
384
|
+
try {
|
|
385
|
+
const leaf = decodeProof(proof)[0];
|
|
386
|
+
return leaf?.kind === "Leaf" ? leaf.value : null;
|
|
387
|
+
} catch {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
355
391
|
function verifyMarfProof(input) {
|
|
356
392
|
const rootToBlock = new Map;
|
|
357
393
|
for (const h of input.headers)
|
|
@@ -982,6 +1018,15 @@ var required = (url, v) => {
|
|
|
982
1018
|
return v;
|
|
983
1019
|
};
|
|
984
1020
|
var trimSlash = (s) => s.replace(/\/+$/, "");
|
|
1021
|
+
var toEpoch2 = (url, j) => {
|
|
1022
|
+
const tip = required(url, j)[0];
|
|
1023
|
+
if (typeof tip?.header !== "string" || typeof tip.consensus_hash !== "string")
|
|
1024
|
+
throw new SourceError(url, 200, "answer needs header and consensus_hash");
|
|
1025
|
+
return {
|
|
1026
|
+
header: unhex(tip.header),
|
|
1027
|
+
consensusHash: unhex(tip.consensus_hash)
|
|
1028
|
+
};
|
|
1029
|
+
};
|
|
985
1030
|
var toMarf = (url, j) => {
|
|
986
1031
|
if (j === null)
|
|
987
1032
|
return null;
|
|
@@ -1018,6 +1063,7 @@ async function pages(field, url, http) {
|
|
|
1018
1063
|
|
|
1019
1064
|
class SecondlayerProofSource {
|
|
1020
1065
|
getBlock;
|
|
1066
|
+
getEpoch2Header;
|
|
1021
1067
|
getMarfProof;
|
|
1022
1068
|
getWitness;
|
|
1023
1069
|
getBurnPreimage;
|
|
@@ -1034,6 +1080,10 @@ class SecondlayerProofSource {
|
|
|
1034
1080
|
const url = typeof ref === "number" ? `${base}/v1/proofs/block/height/${ref}` : `${base}/v1/proofs/block/${ref}`;
|
|
1035
1081
|
return required(url, await getBytes(url, http));
|
|
1036
1082
|
};
|
|
1083
|
+
this.getEpoch2Header = async (blockId) => {
|
|
1084
|
+
const url = `${base}/v1/proofs/epoch2-header/${blockId}`;
|
|
1085
|
+
return toEpoch2(url, await getJson(url, http));
|
|
1086
|
+
};
|
|
1037
1087
|
this.getMarfProof = async (pathHex, tipId) => {
|
|
1038
1088
|
const url = `${base}/v1/proofs/marf/${pathHex}?tip=${tipId}`;
|
|
1039
1089
|
return toMarf(url, await getJson(url, http));
|
|
@@ -1068,6 +1118,7 @@ class SecondlayerProofSource {
|
|
|
1068
1118
|
|
|
1069
1119
|
class NodeRpcProofSource {
|
|
1070
1120
|
getBlock;
|
|
1121
|
+
getEpoch2Header;
|
|
1071
1122
|
getMarfProof;
|
|
1072
1123
|
constructor(opts) {
|
|
1073
1124
|
const base = trimSlash(opts.nodeUrl);
|
|
@@ -1076,6 +1127,10 @@ class NodeRpcProofSource {
|
|
|
1076
1127
|
const url = typeof ref === "number" ? `${base}/v3/blocks/height/${ref}` : `${base}/v3/blocks/${ref}`;
|
|
1077
1128
|
return required(url, await getBytes(url, http));
|
|
1078
1129
|
};
|
|
1130
|
+
this.getEpoch2Header = async (blockId) => {
|
|
1131
|
+
const url = `${base}/v2/headers/1?tip=${blockId}`;
|
|
1132
|
+
return toEpoch2(url, await getJson(url, http));
|
|
1133
|
+
};
|
|
1079
1134
|
this.getMarfProof = async (pathHex, tipId) => {
|
|
1080
1135
|
const url = `${base}/v2/clarity/marf/${pathHex}?tip=${tipId}&proof=1`;
|
|
1081
1136
|
return toMarf(url, await getJson(url, http));
|
|
@@ -1085,7 +1140,6 @@ class NodeRpcProofSource {
|
|
|
1085
1140
|
// src/state.ts
|
|
1086
1141
|
var INTERNAL = {
|
|
1087
1142
|
self: "__MARF_BLOCK_HEIGHT_SELF",
|
|
1088
|
-
heightToHash: "__MARF_BLOCK_HEIGHT_TO_HASH",
|
|
1089
1143
|
hashToHeight: "__MARF_BLOCK_HASH_TO_HEIGHT"
|
|
1090
1144
|
};
|
|
1091
1145
|
var heightValue = (h) => {
|
|
@@ -1096,7 +1150,7 @@ var heightValue = (h) => {
|
|
|
1096
1150
|
var padded = (b32) => `${hex(b32)}${"00".repeat(8)}`;
|
|
1097
1151
|
function internalEntries(height, parentBlockId, leaves) {
|
|
1098
1152
|
const out = [[INTERNAL.self, heightValue(height)]];
|
|
1099
|
-
const ownKey =
|
|
1153
|
+
const ownKey = heightToHashKey(height);
|
|
1100
1154
|
const ownPath = marfPath(ownKey);
|
|
1101
1155
|
const own = leaves.find((l) => bytesEqual(l.path, ownPath));
|
|
1102
1156
|
if (own) {
|
|
@@ -1107,10 +1161,7 @@ function internalEntries(height, parentBlockId, leaves) {
|
|
|
1107
1161
|
]);
|
|
1108
1162
|
}
|
|
1109
1163
|
if (height > 0) {
|
|
1110
|
-
out.push([
|
|
1111
|
-
`${INTERNAL.heightToHash}::${height - 1}`,
|
|
1112
|
-
padded(parentBlockId)
|
|
1113
|
-
]);
|
|
1164
|
+
out.push([heightToHashKey(height - 1), padded(parentBlockId)]);
|
|
1114
1165
|
out.push([
|
|
1115
1166
|
`${INTERNAL.hashToHeight}::${hex(parentBlockId)}`,
|
|
1116
1167
|
heightValue(height - 1)
|
|
@@ -1305,6 +1356,7 @@ async function fetchFor(step, what, call, extra = {}) {
|
|
|
1305
1356
|
}
|
|
1306
1357
|
}
|
|
1307
1358
|
var BITCOIN_BATCH = 2016;
|
|
1359
|
+
var MAX_WALK = 16;
|
|
1308
1360
|
async function findAnchor(low, high, window, probe, maxProbes = 32) {
|
|
1309
1361
|
let [lo, hi] = [low, high];
|
|
1310
1362
|
const target = (window[0] + window[1]) / 2;
|
|
@@ -1339,6 +1391,7 @@ class BlockVerifier {
|
|
|
1339
1391
|
sets = new Map;
|
|
1340
1392
|
anchors = new Map;
|
|
1341
1393
|
headers = new Map;
|
|
1394
|
+
below = new Map;
|
|
1342
1395
|
burns = new Map;
|
|
1343
1396
|
preimages = new Map;
|
|
1344
1397
|
start;
|
|
@@ -1366,23 +1419,38 @@ class BlockVerifier {
|
|
|
1366
1419
|
return out;
|
|
1367
1420
|
}
|
|
1368
1421
|
async run(ref, out) {
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
|
|
1422
|
+
const start = Number(this.start.header.chainLength);
|
|
1423
|
+
if (typeof ref === "number" && ref < start)
|
|
1424
|
+
return this.runBelow(ref, undefined, out);
|
|
1425
|
+
let header;
|
|
1426
|
+
if (typeof ref === "string") {
|
|
1427
|
+
const id = ref.replace(/^0x/, "").toLowerCase();
|
|
1428
|
+
header = await this.header(id, "block");
|
|
1429
|
+
const height = Number(header.chainLength);
|
|
1430
|
+
if (height < start)
|
|
1431
|
+
return this.runBelow(height, id, out);
|
|
1432
|
+
if (isEpoch2Header(header))
|
|
1433
|
+
fail({
|
|
1434
|
+
step: "block",
|
|
1435
|
+
code: "invalid-header",
|
|
1436
|
+
message: `epoch 2.x block ${id} claims height ${height}, at or above the checkpoint`
|
|
1437
|
+
});
|
|
1438
|
+
} else {
|
|
1439
|
+
const raw = await fetchFor("block", `block ${ref}`, () => this.source.getBlock(ref));
|
|
1440
|
+
header = parseHeader2(raw, "block");
|
|
1441
|
+
}
|
|
1372
1442
|
const id = hex(blockId(header));
|
|
1373
1443
|
const height = Number(header.chainLength);
|
|
1374
|
-
if (typeof ref === "string" && id !== ref.replace(/^0x/, "").toLowerCase())
|
|
1375
|
-
fail({
|
|
1376
|
-
step: "block",
|
|
1377
|
-
code: "id-mismatch",
|
|
1378
|
-
message: `source returned block ${id} for ${ref}`
|
|
1379
|
-
});
|
|
1380
1444
|
if (typeof ref === "number" && height !== ref)
|
|
1381
1445
|
fail({
|
|
1382
1446
|
step: "block",
|
|
1383
1447
|
code: "height-mismatch",
|
|
1384
1448
|
message: `source returned height ${height} for ${ref}`
|
|
1385
1449
|
});
|
|
1450
|
+
await this.runForward(header, id, out);
|
|
1451
|
+
}
|
|
1452
|
+
async runForward(header, id, out) {
|
|
1453
|
+
const height = Number(header.chainLength);
|
|
1386
1454
|
out.height = height;
|
|
1387
1455
|
out.blockId = id;
|
|
1388
1456
|
out.stateRoot = hex(header.stateIndexRoot);
|
|
@@ -1401,20 +1469,142 @@ class BlockVerifier {
|
|
|
1401
1469
|
out.threshold = sig.threshold;
|
|
1402
1470
|
if (!sig.valid)
|
|
1403
1471
|
fail(signatureFailure(sig, "signatures", cycle));
|
|
1472
|
+
await this.verifyState(header, id, out);
|
|
1473
|
+
}
|
|
1474
|
+
async runBelow(height, claimed, out) {
|
|
1475
|
+
out.height = height;
|
|
1476
|
+
const { id, ancestry } = await this.ancestor(height);
|
|
1477
|
+
out.ancestry = ancestry;
|
|
1478
|
+
if (claimed !== undefined && claimed !== id)
|
|
1479
|
+
fail({
|
|
1480
|
+
step: "ancestry",
|
|
1481
|
+
code: "not-ancestor",
|
|
1482
|
+
message: `block ${claimed} is not on the checkpoint's chain: its block at height ${height} is ${id}`,
|
|
1483
|
+
blockId: claimed
|
|
1484
|
+
});
|
|
1485
|
+
const header = await this.header(id, "block");
|
|
1486
|
+
if (Number(header.chainLength) !== height)
|
|
1487
|
+
fail({
|
|
1488
|
+
step: "block",
|
|
1489
|
+
code: "height-mismatch",
|
|
1490
|
+
message: `block ${id} claims height ${header.chainLength}, proven at ${height}`
|
|
1491
|
+
});
|
|
1492
|
+
out.blockId = id;
|
|
1493
|
+
out.stateRoot = hex(header.stateIndexRoot);
|
|
1494
|
+
await this.bindBurnBelow(header, out);
|
|
1495
|
+
await this.verifyState(header, id, out);
|
|
1496
|
+
}
|
|
1497
|
+
async ancestor(height) {
|
|
1498
|
+
const known = this.below.get(height);
|
|
1499
|
+
if (known)
|
|
1500
|
+
return known;
|
|
1501
|
+
const from = this.nearestAbove(height);
|
|
1502
|
+
if (from.height - height <= MAX_WALK) {
|
|
1503
|
+
const walked = await this.walk(from, height);
|
|
1504
|
+
if (walked)
|
|
1505
|
+
return walked;
|
|
1506
|
+
}
|
|
1507
|
+
return this.jump(this.nearestAbove(height), height);
|
|
1508
|
+
}
|
|
1509
|
+
nearestAbove(height) {
|
|
1510
|
+
let best = {
|
|
1511
|
+
height: Number(this.start.header.chainLength),
|
|
1512
|
+
id: this.start.id
|
|
1513
|
+
};
|
|
1514
|
+
for (const [h, { id }] of this.below)
|
|
1515
|
+
if (h > height && h < best.height)
|
|
1516
|
+
best = { height: h, id };
|
|
1517
|
+
return best;
|
|
1518
|
+
}
|
|
1519
|
+
async walk(from, height) {
|
|
1520
|
+
const ancestry = {
|
|
1521
|
+
fromHeight: from.height,
|
|
1522
|
+
fromBlockId: from.id,
|
|
1523
|
+
via: "parents"
|
|
1524
|
+
};
|
|
1525
|
+
let child = await this.header(from.id, "ancestry");
|
|
1526
|
+
for (let h = from.height - 1;h >= height; h--) {
|
|
1527
|
+
if (isEpoch2Header(child))
|
|
1528
|
+
return null;
|
|
1529
|
+
const id = hex(child.parentBlockId);
|
|
1530
|
+
const parent = await this.header(id, "ancestry", { blockId: id });
|
|
1531
|
+
if (Number(parent.chainLength) !== h)
|
|
1532
|
+
fail({
|
|
1533
|
+
step: "ancestry",
|
|
1534
|
+
code: "height-mismatch",
|
|
1535
|
+
message: `parent ${id} claims height ${parent.chainLength}, expected ${h}`,
|
|
1536
|
+
blockId: id
|
|
1537
|
+
});
|
|
1538
|
+
this.below.set(h, { id, ancestry });
|
|
1539
|
+
child = parent;
|
|
1540
|
+
}
|
|
1541
|
+
return this.below.get(height) ?? null;
|
|
1542
|
+
}
|
|
1543
|
+
async jump(from, height) {
|
|
1544
|
+
const at = { blockId: from.id };
|
|
1545
|
+
const tip = await this.header(from.id, "ancestry", at);
|
|
1546
|
+
const key = heightToHashKey(height);
|
|
1547
|
+
const path = marfPath(key);
|
|
1548
|
+
const answer = await fetchFor("ancestry", `${key} at ${from.id}`, () => this.source.getMarfProof(hex(path), from.id), at);
|
|
1549
|
+
if (!answer)
|
|
1550
|
+
return fail({
|
|
1551
|
+
step: "ancestry",
|
|
1552
|
+
code: "unavailable",
|
|
1553
|
+
message: `source has no MARF proof of ${key} at block ${from.height}, ${from.height - height} blocks up; parent links reach ${MAX_WALK}`,
|
|
1554
|
+
...at
|
|
1555
|
+
});
|
|
1556
|
+
const value = marfProofValue(answer.proof) ?? new Uint8Array;
|
|
1557
|
+
const proven = value.length === MARF_VALUE_SIZE && value.subarray(32).every((b) => b === 0) && await this.proveAt(tip, path, value, answer.proof, "ancestry", at);
|
|
1558
|
+
if (!proven)
|
|
1559
|
+
return fail({
|
|
1560
|
+
step: "ancestry",
|
|
1561
|
+
code: "ancestry-proof-invalid",
|
|
1562
|
+
message: `${key} is not proven in block ${from.height}'s state`,
|
|
1563
|
+
...at
|
|
1564
|
+
});
|
|
1565
|
+
const ancestry = {
|
|
1566
|
+
fromHeight: from.height,
|
|
1567
|
+
fromBlockId: from.id,
|
|
1568
|
+
via: "marf"
|
|
1569
|
+
};
|
|
1570
|
+
const found = { id: hex(value.subarray(0, 32)), ancestry };
|
|
1571
|
+
this.below.set(height, found);
|
|
1572
|
+
return found;
|
|
1573
|
+
}
|
|
1574
|
+
async bindBurnBelow(header, out) {
|
|
1575
|
+
let hint;
|
|
1576
|
+
try {
|
|
1577
|
+
hint = await this.burnHint(header);
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
out.notes.push(`burn block not checked: ${err.message}`);
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
if (hint < this.chain.checkpointHeight) {
|
|
1583
|
+
out.notes.push(`burn block not checked: its height ${hint} is below the Bitcoin checkpoint ${this.chain.checkpointHeight}`);
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
const burn = await this.bindBurn(header, "burn");
|
|
1587
|
+
out.burnHeight = burn.burnHeight;
|
|
1588
|
+
out.bitcoinBlockHash = burn.hash;
|
|
1589
|
+
}
|
|
1590
|
+
async verifyState(header, id, out) {
|
|
1591
|
+
const src = this.source;
|
|
1592
|
+
const height = Number(header.chainLength);
|
|
1404
1593
|
const witness = await fetchFor("witness", `witness ${id}`, () => src.getWitness(id));
|
|
1405
1594
|
const { getStateWrites, getVmEvents } = this.rows ? src : {};
|
|
1406
1595
|
const writes = getStateWrites ? await fetchFor("names", `state_writes ${height}`, () => getStateWrites.call(src, height)) : null;
|
|
1407
1596
|
const rows = getVmEvents ? await fetchFor("rows", `vm_events ${height}`, () => getVmEvents.call(src, height)) : undefined;
|
|
1408
|
-
const
|
|
1597
|
+
const parent = isEpoch2Header(header) ? epoch2ParentId(witness, header) : header.parentBlockId;
|
|
1598
|
+
const parentId = parent && hex(parent);
|
|
1409
1599
|
const state = await verifyBlockState({
|
|
1410
1600
|
height,
|
|
1411
|
-
parentBlockId:
|
|
1601
|
+
parentBlockId: parent ?? new Uint8Array(32),
|
|
1412
1602
|
stateRoot: header.stateIndexRoot,
|
|
1413
1603
|
witness,
|
|
1414
1604
|
writes,
|
|
1415
1605
|
rows,
|
|
1416
|
-
parentLeaves: await this.parentLeaves(parentId, out.notes),
|
|
1417
|
-
parentHolds: (leaf) => this.parentHolds(parentId, leaf)
|
|
1606
|
+
parentLeaves: parentId ? await this.parentLeaves(parentId, out.notes) : undefined,
|
|
1607
|
+
parentHolds: async (leaf) => parentId ? this.parentHolds(parentId, leaf) : false
|
|
1418
1608
|
});
|
|
1419
1609
|
out.diff = state.diff;
|
|
1420
1610
|
if (rows)
|
|
@@ -1426,8 +1616,7 @@ class BlockVerifier {
|
|
|
1426
1616
|
const known = this.headers.get(id);
|
|
1427
1617
|
if (known)
|
|
1428
1618
|
return known;
|
|
1429
|
-
const
|
|
1430
|
-
const header = parseHeader2(raw, step);
|
|
1619
|
+
const header = await this.fetchHeader(id, step, extra);
|
|
1431
1620
|
if (hex(blockId(header)) !== id)
|
|
1432
1621
|
fail({
|
|
1433
1622
|
step,
|
|
@@ -1439,6 +1628,30 @@ class BlockVerifier {
|
|
|
1439
1628
|
this.headers.set(id, header);
|
|
1440
1629
|
return header;
|
|
1441
1630
|
}
|
|
1631
|
+
async fetchHeader(id, step, extra) {
|
|
1632
|
+
const src = this.source;
|
|
1633
|
+
let raw;
|
|
1634
|
+
try {
|
|
1635
|
+
raw = await src.getBlock(id);
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
const reason = `block ${id}: ${err.message}`;
|
|
1638
|
+
const { getEpoch2Header } = src;
|
|
1639
|
+
if (!getEpoch2Header)
|
|
1640
|
+
return fail({ step, code: "unavailable", message: reason, ...extra });
|
|
1641
|
+
const e2 = await fetchFor(step, `${reason}; epoch 2.x header`, () => getEpoch2Header.call(src, id), extra);
|
|
1642
|
+
try {
|
|
1643
|
+
return parseEpoch2Header(e2.header, e2.consensusHash);
|
|
1644
|
+
} catch (parseErr) {
|
|
1645
|
+
return fail({
|
|
1646
|
+
step,
|
|
1647
|
+
code: "invalid-header",
|
|
1648
|
+
message: `epoch 2.x header ${id} does not parse: ${parseErr.message}`,
|
|
1649
|
+
...extra
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
return parseHeader2(raw, step);
|
|
1654
|
+
}
|
|
1442
1655
|
async proveAt(tip, path, value, proof, step, extra = {}) {
|
|
1443
1656
|
const headers = [tip];
|
|
1444
1657
|
for (const id of marfProofAncestors(proof))
|
|
@@ -1650,6 +1863,20 @@ function parseHeader2(raw, step) {
|
|
|
1650
1863
|
});
|
|
1651
1864
|
}
|
|
1652
1865
|
}
|
|
1866
|
+
function epoch2ParentId(witness, header) {
|
|
1867
|
+
const height = Number(header.chainLength);
|
|
1868
|
+
if (height === 0)
|
|
1869
|
+
return null;
|
|
1870
|
+
try {
|
|
1871
|
+
const w = parseWitness(witness);
|
|
1872
|
+
if (!bytesEqual(w.root, header.stateIndexRoot))
|
|
1873
|
+
return null;
|
|
1874
|
+
const path = marfPath(heightToHashKey(height - 1));
|
|
1875
|
+
return w.leaves.find((l) => bytesEqual(l.path, path))?.valueHash ?? null;
|
|
1876
|
+
} catch {
|
|
1877
|
+
return null;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1653
1880
|
function signatureFailure(sig, step, cycle) {
|
|
1654
1881
|
const weight = `${sig.signedWeight}/${sig.totalWeight} signed, threshold ${sig.threshold}`;
|
|
1655
1882
|
if (sig.unknown.length)
|
|
@@ -1700,10 +1927,12 @@ export {
|
|
|
1700
1927
|
decodeSignerSet,
|
|
1701
1928
|
ftBalanceKey,
|
|
1702
1929
|
hex,
|
|
1930
|
+
isEpoch2Header,
|
|
1703
1931
|
mapEntryKey,
|
|
1704
1932
|
marfPath,
|
|
1705
1933
|
marfProofAncestors,
|
|
1706
1934
|
marfValue,
|
|
1935
|
+
parseEpoch2Header,
|
|
1707
1936
|
parseNakamotoHeader,
|
|
1708
1937
|
parseWitness,
|
|
1709
1938
|
rewardCycle,
|
|
@@ -1716,5 +1945,5 @@ export {
|
|
|
1716
1945
|
verifySignerSignatures
|
|
1717
1946
|
};
|
|
1718
1947
|
|
|
1719
|
-
//# debugId=
|
|
1948
|
+
//# debugId=028F52F18A5FBEDA64756E2164756E21
|
|
1720
1949
|
//# sourceMappingURL=index.js.map
|