@interop/wallet-core 0.69.0 → 0.71.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 +22 -36
- package/dist/clientAnnex/ladder.d.ts +103 -57
- package/dist/clientAnnex/ladder.d.ts.map +1 -1
- package/dist/clientAnnex/ladder.js +254 -335
- package/dist/clientAnnex/ladder.js.map +1 -1
- package/dist/clientAnnex/ladderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/ladderAnchored.js +10 -5
- package/dist/clientAnnex/ladderAnchored.js.map +1 -1
- package/dist/clientAnnex/recoveryLadderAnchored.d.ts +3 -15
- package/dist/clientAnnex/recoveryLadderAnchored.d.ts.map +1 -1
- package/dist/clientAnnex/recoveryLadderAnchored.js +22 -3
- package/dist/clientAnnex/recoveryLadderAnchored.js.map +1 -1
- package/dist/clientAnnex/selfEnroll.js +1 -1
- package/dist/clientAnnex/selfEnroll.js.map +1 -1
- package/dist/clientAnnex/zcap.js +1 -1
- package/dist/clientAnnex/zcap.js.map +1 -1
- package/dist/clients/grantState.d.ts +67 -0
- package/dist/clients/grantState.d.ts.map +1 -0
- package/dist/clients/grantState.js +72 -0
- package/dist/clients/grantState.js.map +1 -0
- package/dist/clients/index.d.ts +7 -0
- package/dist/clients/index.d.ts.map +1 -1
- package/dist/clients/index.js +6 -0
- package/dist/clients/index.js.map +1 -1
- package/dist/enrollment/connectCode.d.ts +4 -3
- package/dist/enrollment/connectCode.d.ts.map +1 -1
- package/dist/enrollment/connectCode.js +4 -3
- package/dist/enrollment/connectCode.js.map +1 -1
- package/dist/enrollment/enrollment.js +1 -1
- package/dist/enrollment/enrollment.js.map +1 -1
- package/dist/enrollment/index.d.ts +2 -2
- package/dist/enrollment/index.js +2 -2
- package/dist/enrollment/onboardingInvite.d.ts +1 -1
- package/dist/enrollment/onboardingInvite.js +1 -1
- package/dist/enrollment/onboardingResponse.js +1 -1
- package/dist/index.d.ts +4 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -11
- package/dist/index.js.map +1 -1
- package/dist/keyring/index.d.ts +1 -1
- package/dist/keyring/index.js +1 -1
- package/dist/keyring/kdf.d.ts +34 -20
- package/dist/keyring/kdf.d.ts.map +1 -1
- package/dist/keyring/kdf.js +37 -26
- package/dist/keyring/kdf.js.map +1 -1
- package/dist/keys/userKey.js +1 -1
- package/dist/keys/userKey.js.map +1 -1
- package/dist/recovery/continuation.d.ts +5 -4
- package/dist/recovery/continuation.d.ts.map +1 -1
- package/dist/recovery/continuation.js +14 -13
- package/dist/recovery/continuation.js.map +1 -1
- package/dist/resourceLog/document.d.ts +10 -2
- package/dist/resourceLog/document.d.ts.map +1 -1
- package/dist/resourceLog/document.js +1 -2
- package/dist/resourceLog/document.js.map +1 -1
- package/dist/space/index.d.ts +4 -2
- package/dist/space/index.d.ts.map +1 -1
- package/dist/space/index.js +4 -2
- package/dist/space/index.js.map +1 -1
- package/dist/space/wasLink.d.ts +13 -2
- package/dist/space/wasLink.d.ts.map +1 -1
- package/dist/space/wasLink.js +28 -2
- package/dist/space/wasLink.js.map +1 -1
- package/dist/unlock/index.d.ts +1 -1
- package/dist/unlock/index.d.ts.map +1 -1
- package/dist/unlock/index.js.map +1 -1
- package/dist/unlock/standingClient.d.ts +1 -1
- package/dist/unlock/standingClient.d.ts.map +1 -1
- package/dist/unlock/standingClient.js +3 -3
- package/dist/unlock/standingClient.js.map +1 -1
- package/dist/unlock/standingWebvh.d.ts +81 -19
- package/dist/unlock/standingWebvh.d.ts.map +1 -1
- package/dist/unlock/standingWebvh.js +170 -30
- package/dist/unlock/standingWebvh.js.map +1 -1
- package/dist/webvh/did.d.ts +2 -4
- package/dist/webvh/did.d.ts.map +1 -1
- package/dist/webvh/did.js +2 -4
- package/dist/webvh/did.js.map +1 -1
- package/dist/webvh/didWebvh.d.ts +3 -2
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +2 -1
- package/dist/webvh/didWebvh.js.map +1 -1
- package/dist/webvh/index.d.ts +1 -1
- package/dist/webvh/index.js +1 -1
- package/dist/webvh/revokeClient.d.ts +13 -7
- package/dist/webvh/revokeClient.d.ts.map +1 -1
- package/dist/webvh/revokeClient.js +14 -7
- package/dist/webvh/revokeClient.js.map +1 -1
- package/dist/webvh/zcap.js +1 -1
- package/dist/webvh/zcap.js.map +1 -1
- package/package.json +2 -23
- package/dist/identity/agents.d.ts +0 -100
- package/dist/identity/agents.d.ts.map +0 -1
- package/dist/identity/agents.js +0 -120
- package/dist/identity/agents.js.map +0 -1
- package/dist/identity/index.d.ts +0 -24
- package/dist/identity/index.d.ts.map +0 -1
- package/dist/identity/index.js +0 -23
- package/dist/identity/index.js.map +0 -1
- package/dist/identity/keyResolver.d.ts +0 -30
- package/dist/identity/keyResolver.d.ts.map +0 -1
- package/dist/identity/keyResolver.js +0 -25
- package/dist/identity/keyResolver.js.map +0 -1
- package/dist/request/appKey.d.ts +0 -295
- package/dist/request/appKey.d.ts.map +0 -1
- package/dist/request/appKey.js +0 -516
- package/dist/request/appKey.js.map +0 -1
- package/dist/request/capabilityRequest.d.ts +0 -41
- package/dist/request/capabilityRequest.d.ts.map +0 -1
- package/dist/request/capabilityRequest.js +0 -46
- package/dist/request/capabilityRequest.js.map +0 -1
- package/dist/request/classify.d.ts +0 -226
- package/dist/request/classify.d.ts.map +0 -1
- package/dist/request/classify.js +0 -416
- package/dist/request/classify.js.map +0 -1
- package/dist/request/composeVp.d.ts +0 -52
- package/dist/request/composeVp.d.ts.map +0 -1
- package/dist/request/composeVp.js +0 -176
- package/dist/request/composeVp.js.map +0 -1
- package/dist/request/ephemeralExchange.d.ts +0 -100
- package/dist/request/ephemeralExchange.d.ts.map +0 -1
- package/dist/request/ephemeralExchange.js +0 -230
- package/dist/request/ephemeralExchange.js.map +0 -1
- package/dist/request/exchangeClient.d.ts +0 -142
- package/dist/request/exchangeClient.d.ts.map +0 -1
- package/dist/request/exchangeClient.js +0 -241
- package/dist/request/exchangeClient.js.map +0 -1
- package/dist/request/index.d.ts +0 -59
- package/dist/request/index.d.ts.map +0 -1
- package/dist/request/index.js +0 -59
- package/dist/request/index.js.map +0 -1
- package/dist/request/interactionRequest.d.ts +0 -23
- package/dist/request/interactionRequest.d.ts.map +0 -1
- package/dist/request/interactionRequest.js +0 -46
- package/dist/request/interactionRequest.js.map +0 -1
- package/dist/request/interactionUrl.d.ts +0 -42
- package/dist/request/interactionUrl.d.ts.map +0 -1
- package/dist/request/interactionUrl.js +0 -104
- package/dist/request/interactionUrl.js.map +0 -1
- package/dist/request/matching.d.ts +0 -95
- package/dist/request/matching.d.ts.map +0 -1
- package/dist/request/matching.js +0 -205
- package/dist/request/matching.js.map +0 -1
- package/dist/request/onboarding.d.ts +0 -94
- package/dist/request/onboarding.d.ts.map +0 -1
- package/dist/request/onboarding.js +0 -167
- package/dist/request/onboarding.js.map +0 -1
- package/dist/request/parse.d.ts +0 -63
- package/dist/request/parse.d.ts.map +0 -1
- package/dist/request/parse.js +0 -153
- package/dist/request/parse.js.map +0 -1
- package/dist/request/presentationSuite.d.ts +0 -71
- package/dist/request/presentationSuite.d.ts.map +0 -1
- package/dist/request/presentationSuite.js +0 -143
- package/dist/request/presentationSuite.js.map +0 -1
- package/dist/request/processRequest.d.ts +0 -45
- package/dist/request/processRequest.d.ts.map +0 -1
- package/dist/request/processRequest.js +0 -147
- package/dist/request/processRequest.js.map +0 -1
- package/dist/request/queryPredicates.d.ts +0 -98
- package/dist/request/queryPredicates.d.ts.map +0 -1
- package/dist/request/queryPredicates.js +0 -107
- package/dist/request/queryPredicates.js.map +0 -1
- package/dist/request/types.d.ts +0 -257
- package/dist/request/types.d.ts.map +0 -1
- package/dist/request/types.js +0 -2
- package/dist/request/types.js.map +0 -1
- package/dist/request/walletInput.d.ts +0 -93
- package/dist/request/walletInput.d.ts.map +0 -1
- package/dist/request/walletInput.js +0 -179
- package/dist/request/walletInput.js.map +0 -1
|
@@ -40,8 +40,7 @@ import { vmFragmentOf } from '@interop/vh-resource-log';
|
|
|
40
40
|
import { hkdf } from '@noble/hashes/hkdf.js';
|
|
41
41
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
42
42
|
import { currentLogParameters, effectiveParameters, updateKeyMultibase } from '../webvh/didWebvh.js';
|
|
43
|
-
import {
|
|
44
|
-
import { credentialKeyAgreementMethods, introducedCredentialKeys, ladderVmIds, relationIds, resolvedKeyAgreementMethods, retiredCredentialKeys } from '../resourceLog/document.js';
|
|
43
|
+
import { credentialKeyAgreementMethods, introducedCredentialKeys, ladderVmIds, relationIds } from '../resourceLog/document.js';
|
|
45
44
|
import { survivingClientKeyProtection } from '../webvh/revokeClient.js';
|
|
46
45
|
import { log as logger } from '../log.js';
|
|
47
46
|
import { LADDER_SEED_BYTES } from '../unlock/unlockRecord.js';
|
|
@@ -351,6 +350,25 @@ function credentialSurvives({ entry, vmId }) {
|
|
|
351
350
|
// commitment.
|
|
352
351
|
return relationIds(entry.state.keyAgreement).includes(vmId);
|
|
353
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Whether the document an entry publishes carries a ladder VM the previous
|
|
355
|
+
* entry's did not -- the install half of a bind, a split issuance's authority
|
|
356
|
+
* entry, or a reinstall.
|
|
357
|
+
*
|
|
358
|
+
* @param options {object}
|
|
359
|
+
* @param options.log {DIDLog}
|
|
360
|
+
* @param options.index {number} the entry's index in the log
|
|
361
|
+
* @returns {boolean}
|
|
362
|
+
*/
|
|
363
|
+
function introducesLadderVm({ log, index }) {
|
|
364
|
+
const doc = log[index]?.state;
|
|
365
|
+
const prevDoc = log[index - 1]?.state;
|
|
366
|
+
if (doc === undefined) {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
const standing = new Set(prevDoc === undefined ? [] : ladderVmIds({ doc: prevDoc }));
|
|
370
|
+
return ladderVmIds({ doc }).some(vmId => !standing.has(vmId));
|
|
371
|
+
}
|
|
354
372
|
/**
|
|
355
373
|
* The log-derived indexes every attribution walk starts from, computed once
|
|
356
374
|
* per log rather than once per credential.
|
|
@@ -382,23 +400,6 @@ function indexedLadderLog(log) {
|
|
|
382
400
|
ladderLogIndexes.set(log, indexed);
|
|
383
401
|
return indexed;
|
|
384
402
|
}
|
|
385
|
-
/**
|
|
386
|
-
* The account's enrolled clients as the log attributes them, memoized on the
|
|
387
|
-
* log for the same reason as {@link indexedLadderLog}.
|
|
388
|
-
*
|
|
389
|
-
* @param log {DIDLog}
|
|
390
|
-
* @returns {ReturnType<typeof listEnrolledWebvhClients>}
|
|
391
|
-
*/
|
|
392
|
-
const enrolledClientsByLog = new WeakMap();
|
|
393
|
-
function indexedEnrolledClients(log) {
|
|
394
|
-
const cached = enrolledClientsByLog.get(log);
|
|
395
|
-
if (cached) {
|
|
396
|
-
return cached;
|
|
397
|
-
}
|
|
398
|
-
const clients = listEnrolledWebvhClients({ log });
|
|
399
|
-
enrolledClientsByLog.set(log, clients);
|
|
400
|
-
return clients;
|
|
401
|
-
}
|
|
402
403
|
/**
|
|
403
404
|
* The one pre-pass over the log's effective parameters: per-entry facts, plus
|
|
404
405
|
* the commit index both walks project their positional questions through. The
|
|
@@ -451,7 +452,16 @@ function indexLadderLog({ log, params }) {
|
|
|
451
452
|
* that entry is the rung before it. The step is taken only when the entry
|
|
452
453
|
* authorized exactly one key and that key signed the entry, which makes it a
|
|
453
454
|
* prerotation reveal rather than mere adjacency, and only when the credential
|
|
454
|
-
* itself still stands in the entry's document.
|
|
455
|
+
* itself still stands in the entry's document. One more gate tells a climb
|
|
456
|
+
* from a bind. An entry that installs the credential's inventory -- its
|
|
457
|
+
* `keyAgreement` member, or a ladder VM not standing before -- is where the
|
|
458
|
+
* ladder is being bound, and the rung that signs a bind is the credential's
|
|
459
|
+
* own only where the ladder is born there: the signer's own hash is newly
|
|
460
|
+
* committed in that same entry, the ladder-anchored genesis shape. A signer
|
|
461
|
+
* whose hash an earlier entry committed is a sibling ladder's rung revealing
|
|
462
|
+
* itself in the entry it signs on the newcomer's behalf (the ladder-signed
|
|
463
|
+
* `publishUnlockKey` bind, a split issuance's authority entry), and the walk
|
|
464
|
+
* stops rather than annexing it.
|
|
455
465
|
*
|
|
456
466
|
* The ADJACENCY rule is a handover. A hash not in last position sits beside
|
|
457
467
|
* the rung committed immediately before it, and that predecessor is revealed
|
|
@@ -463,15 +473,19 @@ function indexLadderLog({ log, params }) {
|
|
|
463
473
|
* ours yet, and at the spent recovery code whose reveal entry commits the
|
|
464
474
|
* REPLACEMENT code's hash last -- without it the replacement's retirement
|
|
465
475
|
* would recover the spent code's key and go on to strike the fresh
|
|
466
|
-
* credential's rungs. That last stop is load-bearing for
|
|
467
|
-
*
|
|
468
|
-
* enters
|
|
469
|
-
*
|
|
470
|
-
*
|
|
476
|
+
* credential's rungs. That last stop is load-bearing for a replacement code:
|
|
477
|
+
* its member's `ladderCommitment` is that last-position hash, so its walk
|
|
478
|
+
* enters here through the climb rule, and its member is published one entry
|
|
479
|
+
* after the reveal that committed the hash, which is exactly what the test
|
|
480
|
+
* refuses on. The single-self-revealing-key test stops it at the bind
|
|
471
481
|
* entry an enrolled client signs, which authorizes no key of its own, so the
|
|
472
|
-
* binding client's update key is never recovered as a rung. The
|
|
473
|
-
*
|
|
474
|
-
*
|
|
482
|
+
* binding client's update key is never recovered as a rung. The ladder-birth
|
|
483
|
+
* test stops it at the bind entry a sibling's committed rung signs, which
|
|
484
|
+
* authorizes exactly that rung, so the acting credential's rung is never
|
|
485
|
+
* recovered as the bound credential's -- the over-strike that would leave
|
|
486
|
+
* the acting credential unable to extend the log. The strictly decreasing
|
|
487
|
+
* entry cursor and the already-recovered test keep the walk finite and
|
|
488
|
+
* acyclic.
|
|
475
489
|
*
|
|
476
490
|
* @param options {object}
|
|
477
491
|
* @param options.log {DIDLog}
|
|
@@ -498,16 +512,24 @@ async function recoverEarlierRungs({ log, facts, commitIndex, anchorHash, creden
|
|
|
498
512
|
if (origin.at === originFacts.addedHashes.length - 1) {
|
|
499
513
|
// The last-position rule, read backwards: a climb.
|
|
500
514
|
const predecessor = originFacts.addedKeys[0];
|
|
515
|
+
const originEntry = log[origin.entryIndex];
|
|
501
516
|
if (originFacts.addedKeys.length !== 1 ||
|
|
502
517
|
predecessor === undefined ||
|
|
503
518
|
!originFacts.signers.includes(predecessor) ||
|
|
504
|
-
!credentialSurvives({
|
|
505
|
-
entry: log[origin.entryIndex],
|
|
506
|
-
vmId: credentialVmId
|
|
507
|
-
})) {
|
|
519
|
+
!credentialSurvives({ entry: originEntry, vmId: credentialVmId })) {
|
|
508
520
|
return recovered;
|
|
509
521
|
}
|
|
510
522
|
const hash = await deriveNextKeyHash(predecessor);
|
|
523
|
+
// The ladder-birth test: an entry installing the credential's inventory
|
|
524
|
+
// is a bind, whose signer is this ladder's rung only where the ladder
|
|
525
|
+
// is born there -- its own hash committed in the same entry.
|
|
526
|
+
const installsInventory = !credentialSurvives({
|
|
527
|
+
entry: log[origin.entryIndex - 1],
|
|
528
|
+
vmId: credentialVmId
|
|
529
|
+
}) || introducesLadderVm({ log, index: origin.entryIndex });
|
|
530
|
+
if (installsInventory && !originFacts.addedHashes.includes(hash)) {
|
|
531
|
+
return recovered;
|
|
532
|
+
}
|
|
511
533
|
if (seenHashes.has(hash)) {
|
|
512
534
|
return recovered;
|
|
513
535
|
}
|
|
@@ -597,84 +619,173 @@ export function assertNextKeyHashesRemain({ nextKeyHashes, ceremony }) {
|
|
|
597
619
|
return nextKeyHashes;
|
|
598
620
|
}
|
|
599
621
|
/**
|
|
600
|
-
* The
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
622
|
+
* The anchor a credential's ladder walk starts from when the caller holds no
|
|
623
|
+
* recorded update key -- the log-only anchoring a cold browser needs. Every
|
|
624
|
+
* credential-class `keyAgreement` member names its own ladder's rung-0
|
|
625
|
+
* commitment (`ladderCommitment`, written by every bind site through
|
|
626
|
+
* `unlockKeyVerificationMethod`), so the anchor is read off the member
|
|
627
|
+
* itself rather than inferred from the shape of the entry that introduced
|
|
628
|
+
* it. The value is `hash(rung 0)` in the form `nextKeyHashes` carries, the
|
|
629
|
+
* same commitment the bind puts there.
|
|
630
|
+
*
|
|
631
|
+
* The value is taken from the entry that INTRODUCED the member, and only
|
|
632
|
+
* from an account-controlled member (an enrolled client's marked twin is
|
|
633
|
+
* never a credential). A member is introduced each time it appears after
|
|
634
|
+
* not standing, so a member re-bound after a retirement (the same passphrase
|
|
635
|
+
* added again) anchors on the fresh ladder's commitment, and a log read
|
|
636
|
+
* before the retirement entry that struck it still names it. What a later
|
|
637
|
+
* entry says about a member that already stood is checked, not adopted: the
|
|
638
|
+
* property is a document member any update-key holder can restate, and a
|
|
639
|
+
* value that changes while the member stands continuously is a retargeting
|
|
640
|
+
* no bind performs. Such a member names no anchor for the rest of that
|
|
641
|
+
* standing run, so the retargeting is reported as unclaimed rather than
|
|
642
|
+
* quietly walked from the substituted hash. The write side carries the same
|
|
643
|
+
* rule: `publishUnlockKey`, the one path in this library that rewrites a
|
|
644
|
+
* standing member, refuses a bind whose rung-0 hash differs from the value
|
|
645
|
+
* the member already names, so a retargeting in a served log is a foreign
|
|
646
|
+
* writer's and never a torn re-run's. The guard covers only a
|
|
647
|
+
* continuous run: the same update-key holder can strike the member in one
|
|
648
|
+
* entry and re-add it under a fresh commitment in the next, which reads as a
|
|
649
|
+
* legitimate re-bind and is not distinguishable from one seedlessly. That
|
|
650
|
+
* shape rests on the log's loudness and on `survivingClientKeyProtection`
|
|
651
|
+
* instead. A member without the property, or one nowhere in the log, names
|
|
652
|
+
* no anchor either.
|
|
653
|
+
*
|
|
654
|
+
* The named hash must also be one the log COMMITTED FOR THE MEMBER: newly
|
|
655
|
+
* added to `nextKeyHashes` by the introducing entry (the merged bind, the
|
|
656
|
+
* ladder-anchored genesis), by a later entry of the same standing run (the
|
|
657
|
+
* split bind's authority entry, two versions after its key entry), or by an
|
|
658
|
+
* earlier entry whose signer the introducing entry retires (the recovery
|
|
659
|
+
* continuations' reveal-and-commit entry, signed by the spent code's rung,
|
|
660
|
+
* which the add-and-retire entry introducing the replacement's member and
|
|
661
|
+
* the fresh credential's strikes -- every reveal entry of a torn and resumed
|
|
662
|
+
* continuation is that shape). A member naming a hash that stood in
|
|
663
|
+
* `nextKeyHashes` already, committed by some other entry for something else
|
|
664
|
+
* -- an enrolled client's staged hash, say, which any update-key holder can
|
|
665
|
+
* read off the log and restate as a commitment -- names no anchor. Without
|
|
666
|
+
* the check the walk would claim that hash for the credential, and a
|
|
667
|
+
* retirement that honored the claim would strike a surviving client's
|
|
668
|
+
* staged commitment. The check is necessary rather than sufficient: an
|
|
669
|
+
* approver can name a hash BEFORE the enrollment it approves commits it, and
|
|
670
|
+
* that is the shape `survivingClientKeyProtection`'s positional exemption
|
|
671
|
+
* closes. Every refusal returns `undefined`, which the callers report as
|
|
672
|
+
* unclaimed rather than acting on.
|
|
606
673
|
*
|
|
607
674
|
* @param options {object}
|
|
608
|
-
* @param options.
|
|
609
|
-
* @param
|
|
610
|
-
*
|
|
611
|
-
* @returns {
|
|
675
|
+
* @param options.log {DIDLog} a resolved, caller-verified log
|
|
676
|
+
* @param options.credentialVmId {string} the credential's `keyAgreement`
|
|
677
|
+
* verification-method id
|
|
678
|
+
* @returns {{ anchorHash: string } | undefined}
|
|
612
679
|
*/
|
|
613
|
-
function
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
const credential = new Set(credentialKeyAgreementMethods({ doc: entryDoc, did }).map(method => method.id));
|
|
623
|
-
return new Set(resolvedKeyAgreementMethods({ doc: entryDoc })
|
|
624
|
-
.map(method => method.id)
|
|
625
|
-
.filter((id) => id !== undefined && !credential.has(id)));
|
|
626
|
-
};
|
|
627
|
-
const before = markedIds(prevDoc);
|
|
628
|
-
return [...markedIds(doc)].some(id => !before.has(id));
|
|
680
|
+
export function credentialLadderAnchor({ log, credentialVmId }) {
|
|
681
|
+
const reading = credentialLadderCommitment({ log, credentialVmId });
|
|
682
|
+
return reading === undefined ||
|
|
683
|
+
reading.named === undefined ||
|
|
684
|
+
reading.retargeted ||
|
|
685
|
+
!reading.committed
|
|
686
|
+
? undefined
|
|
687
|
+
: { anchorHash: reading.named };
|
|
629
688
|
}
|
|
630
689
|
/**
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* entry
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
* prerotation reveal, the ladder-anchored genesis shape), so rung 0 is that
|
|
641
|
-
* key outright;
|
|
642
|
-
* - the entry authorized no key of its own and newly committed exactly one
|
|
643
|
-
* hash (the `publishUnlockKey` bind an enrolled client signs, and the
|
|
644
|
-
* recovery-code issuance sharing it), so rung 0's hash is that hash.
|
|
645
|
-
*
|
|
646
|
-
* A third shape is the recovery add-and-retire entry, which the two above
|
|
647
|
-
* cannot read: the transient continuation's introduces the fresh credential's
|
|
648
|
-
* member and the replacement code's together, and the remembered
|
|
649
|
-
* continuation's introduces the replacement's beside an enrolled client. Both
|
|
650
|
-
* are the HANDOVER of `decisions/0007`, read here as the anchor rule of
|
|
651
|
-
* `decisions/0014`: the entry authorized exactly one key, that key signed it,
|
|
652
|
-
* that key's hash was committed by an earlier reveal-and-commit entry whose
|
|
653
|
-
* signer this entry retires, and that earlier entry appended exactly three
|
|
654
|
-
* hashes with the successor's first. The reveal entry's LAST addition is then
|
|
655
|
-
* the replacement code's rung-0 hash (what an entry hands to a successor
|
|
656
|
-
* credential comes last), and the successor key is the fresh credential's
|
|
657
|
-
* rung 0. Which member is which is read off the `keyAgreement` relation's
|
|
658
|
-
* order, which the emitter fixes: the fresh credential's member precedes the
|
|
659
|
-
* replacement code's. So a two-member bind that publishes no enrolled client
|
|
660
|
-
* anchors its first member on the successor key and its second on the last
|
|
661
|
-
* addition; a one-member bind that publishes an enrolled client anchors that
|
|
662
|
-
* member on the last addition alone, since the successor key there is the
|
|
663
|
-
* client's.
|
|
664
|
-
*
|
|
665
|
-
* Anything else is ambiguous and returns `undefined`, which the callers report
|
|
666
|
-
* as unclaimed rather than acting on.
|
|
690
|
+
* What the log says about a credential member's `ladderCommitment`, before
|
|
691
|
+
* {@link credentialLadderAnchor} decides whether it is an anchor: the value
|
|
692
|
+
* the entry introducing the member's latest standing run named (`named`),
|
|
693
|
+
* whether the member stands in the head document (`stands`), whether a later
|
|
694
|
+
* entry of that run restated the value (`retargeted`), and whether the log
|
|
695
|
+
* committed the named hash for the member (`committed`). The anchor reader
|
|
696
|
+
* is the refusing composition of these; the bind's write-side check reads
|
|
697
|
+
* them apart, since a split bind's authority entry meets a member that names
|
|
698
|
+
* its hash while the log has not committed it yet.
|
|
667
699
|
*
|
|
668
700
|
* @param options {object}
|
|
669
701
|
* @param options.log {DIDLog} a resolved, caller-verified log
|
|
670
702
|
* @param options.credentialVmId {string} the credential's `keyAgreement`
|
|
671
703
|
* verification-method id
|
|
672
|
-
* @returns {
|
|
673
|
-
* undefined
|
|
704
|
+
* @returns {{ named?: string, stands: boolean, retargeted: boolean,
|
|
705
|
+
* committed: boolean } | undefined} `undefined` for a member the log
|
|
706
|
+
* never introduced
|
|
674
707
|
*/
|
|
675
|
-
export
|
|
676
|
-
const
|
|
677
|
-
|
|
708
|
+
export function credentialLadderCommitment({ log, credentialVmId }) {
|
|
709
|
+
const did = credentialVmId.split('#')[0];
|
|
710
|
+
if (did === undefined || did === '') {
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
713
|
+
const { facts } = indexedLadderLog(log);
|
|
714
|
+
let introduced = false;
|
|
715
|
+
let named;
|
|
716
|
+
let stood = false;
|
|
717
|
+
let retargeted = false;
|
|
718
|
+
let committed = false;
|
|
719
|
+
for (const [index, entry] of log.entries()) {
|
|
720
|
+
const doc = entry.state;
|
|
721
|
+
if (doc === undefined) {
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
const member = credentialKeyAgreementMethods({ doc, did }).find(method => method.id === credentialVmId);
|
|
725
|
+
if (member === undefined) {
|
|
726
|
+
stood = false;
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
const value = typeof member.ladderCommitment === 'string' &&
|
|
730
|
+
member.ladderCommitment !== ''
|
|
731
|
+
? member.ladderCommitment
|
|
732
|
+
: undefined;
|
|
733
|
+
const here = facts[index];
|
|
734
|
+
if (!stood) {
|
|
735
|
+
// Introduced here (or re-introduced after a strike): the bind's word.
|
|
736
|
+
introduced = true;
|
|
737
|
+
named = value;
|
|
738
|
+
retargeted = false;
|
|
739
|
+
committed =
|
|
740
|
+
value !== undefined &&
|
|
741
|
+
(here?.addedHashes.includes(value) === true ||
|
|
742
|
+
committedByRetiredSigner({ facts, index, hash: value }));
|
|
743
|
+
}
|
|
744
|
+
else if (value !== named) {
|
|
745
|
+
retargeted = true;
|
|
746
|
+
}
|
|
747
|
+
else if (!committed && value !== undefined) {
|
|
748
|
+
committed = here?.addedHashes.includes(value) === true;
|
|
749
|
+
}
|
|
750
|
+
stood = true;
|
|
751
|
+
}
|
|
752
|
+
return introduced
|
|
753
|
+
? {
|
|
754
|
+
...(named !== undefined ? { named } : {}),
|
|
755
|
+
stands: stood,
|
|
756
|
+
retargeted,
|
|
757
|
+
committed
|
|
758
|
+
}
|
|
759
|
+
: undefined;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Whether an entry BEFORE `index` newly committed `hash` and was signed by an
|
|
763
|
+
* update key the entry at `index` retires -- the handover shape the recovery
|
|
764
|
+
* continuations leave: the spent code's rung signs the reveal-and-commit
|
|
765
|
+
* entry that commits the successors' hashes, and the add-and-retire entry
|
|
766
|
+
* that introduces their members strikes that rung. A torn and resumed
|
|
767
|
+
* continuation leaves several reveal entries, every one signed by the same
|
|
768
|
+
* rung, so the earliest one's commitments qualify as well as the last one's.
|
|
769
|
+
*
|
|
770
|
+
* @param options {object}
|
|
771
|
+
* @param options.facts {LadderEntryFacts[]} from {@link indexLadderLog}
|
|
772
|
+
* @param options.index {number} the introducing entry
|
|
773
|
+
* @param options.hash {string} the member's named commitment
|
|
774
|
+
* @returns {boolean}
|
|
775
|
+
*/
|
|
776
|
+
function committedByRetiredSigner({ facts, index, hash }) {
|
|
777
|
+
const retired = new Set(facts[index]?.removedKeys ?? []);
|
|
778
|
+
if (retired.size === 0) {
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
for (let earlier = index - 1; earlier >= 0; earlier--) {
|
|
782
|
+
const entry = facts[earlier];
|
|
783
|
+
if (entry.addedHashes.includes(hash) &&
|
|
784
|
+
entry.signers.some(signer => retired.has(signer))) {
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return false;
|
|
678
789
|
}
|
|
679
790
|
/**
|
|
680
791
|
* The ladder VMs the log introduced ALONGSIDE something of this credential's
|
|
@@ -770,12 +881,11 @@ export async function ladderVmIdsIntroducedWithCredential({ log, credentialVmId,
|
|
|
770
881
|
* another resumed reveal, walked past (a continuation torn at its seam twice
|
|
771
882
|
* leaves one behind). A three-addition one is an attempt that committed a
|
|
772
883
|
* replacement, and its last addition is collected. Any other size is no
|
|
773
|
-
* attempt of this continuation's, and the answer is refused. The
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
* resumes, which the contract forbids and which no last position can settle.
|
|
884
|
+
* attempt of this continuation's, and the answer is refused. The forward walk
|
|
885
|
+
* asks only whether an attempt committed a replacement at all, so the
|
|
886
|
+
* two-addition entry's last position is the ladder's own rung. Which hash is
|
|
887
|
+
* the replacement's is never read from here: its member names it
|
|
888
|
+
* (`ladderCommitment`).
|
|
779
889
|
*
|
|
780
890
|
* @param options {object}
|
|
781
891
|
* @param options.facts {LadderEntryFacts[]} from {@link indexLadderLog}
|
|
@@ -802,212 +912,9 @@ function resumedRevealReplacementHashes({ facts, before, retiredSigners }) {
|
|
|
802
912
|
}
|
|
803
913
|
return replacementHashes;
|
|
804
914
|
}
|
|
805
|
-
/**
|
|
806
|
-
* The recovery add-and-retire arm of {@link resolveBindAnchor}: the handover
|
|
807
|
-
* shape of `decisions/0007` read as an anchor, per `decisions/0014`. Every
|
|
808
|
-
* test is fail-closed. A shape that passes them all names the successor key
|
|
809
|
-
* the entry authorized and, when the reveal entry that committed its hash
|
|
810
|
-
* (or, on a resumed reveal, the attempt before it) ends on one, the
|
|
811
|
-
* replacement code's hash. The two are decoupled on purpose: the successor
|
|
812
|
-
* key is unambiguous from the bind entry alone, so a replacement lookup that
|
|
813
|
-
* refuses leaves the fresh credential anchored and retirable.
|
|
814
|
-
*
|
|
815
|
-
* The between-entries test is what refuses a remembered continuation whose
|
|
816
|
-
* reveal entry was published twice with a DIFFERENT replacement code, which
|
|
817
|
-
* the continuation's contract forbids. The retired signer then signed an
|
|
818
|
-
* entry after the reveal, and no last addition is the replacement's. A reveal
|
|
819
|
-
* published twice with the SAME replacement is the resumed shape
|
|
820
|
-
* {@link resumedRevealReplacementHashes} reads.
|
|
821
|
-
*
|
|
822
|
-
* The retired-member test is what keeps the arm off a self-enrollment's add
|
|
823
|
-
* entry, which passes every other clause: its reveal entry also commits
|
|
824
|
-
* exactly three hashes with the successor's first, and its add entry also
|
|
825
|
-
* authorizes one self-signing key while retiring the reveal's signer. Only a
|
|
826
|
-
* spend strikes a credential-class `keyAgreement` member in that entry, and
|
|
827
|
-
* a self-enrollment's third hash is the acting ladder's own next rung, not a
|
|
828
|
-
* successor's.
|
|
829
|
-
*
|
|
830
|
-
* @param options {object}
|
|
831
|
-
* @param options.facts {LadderEntryFacts[]} from {@link indexLadderLog}
|
|
832
|
-
* @param options.commitIndex {Map<string, LadderCommitOrigin>} likewise
|
|
833
|
-
* @param options.bind {LadderEntryFacts} the bind entry's facts
|
|
834
|
-
* @param options.index {number} the bind entry's index
|
|
835
|
-
* @param options.successorKeyMultibase {string} the one key it authorized
|
|
836
|
-
* @param options.successorHash {string} that key's hash
|
|
837
|
-
* @param options.retiresCredentialMember {boolean} whether the bind entry
|
|
838
|
-
* struck a credential-class `keyAgreement` member (the spent code's)
|
|
839
|
-
* @returns {{ successorKeyMultibase: string, replacementHash?: string } |
|
|
840
|
-
* undefined}
|
|
841
|
-
*/
|
|
842
|
-
function resolveHandoverShape({ facts, commitIndex, bind, index, successorKeyMultibase, successorHash, retiresCredentialMember }) {
|
|
843
|
-
if (!retiresCredentialMember ||
|
|
844
|
-
bind.addedKeys.length !== 1 ||
|
|
845
|
-
!bind.signers.includes(successorKeyMultibase)) {
|
|
846
|
-
return undefined;
|
|
847
|
-
}
|
|
848
|
-
// The reveal entry: it committed the successor's hash first among exactly
|
|
849
|
-
// three additions (or two, on a resumed reveal), and this entry retires a
|
|
850
|
-
// key that signed it.
|
|
851
|
-
const origin = commitIndex.get(successorHash);
|
|
852
|
-
if (origin === undefined || origin.entryIndex >= index || origin.at !== 0) {
|
|
853
|
-
return undefined;
|
|
854
|
-
}
|
|
855
|
-
const reveal = facts[origin.entryIndex];
|
|
856
|
-
const retiredSigners = reveal.signers.filter(signer => bind.removedKeys.includes(signer));
|
|
857
|
-
if (retiredSigners.length === 0) {
|
|
858
|
-
return undefined;
|
|
859
|
-
}
|
|
860
|
-
for (let between = origin.entryIndex + 1; between < index; between++) {
|
|
861
|
-
if (facts[between].signers.some(key => retiredSigners.includes(key))) {
|
|
862
|
-
return undefined;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
const earlier = reveal.addedHashes.length === 2
|
|
866
|
-
? resumedRevealReplacementHashes({
|
|
867
|
-
facts,
|
|
868
|
-
before: origin.entryIndex,
|
|
869
|
-
retiredSigners
|
|
870
|
-
})
|
|
871
|
-
: undefined;
|
|
872
|
-
const replacementHash = reveal.addedHashes.length === 3
|
|
873
|
-
? reveal.addedHashes[2]
|
|
874
|
-
: earlier?.length === 1
|
|
875
|
-
? earlier[0]
|
|
876
|
-
: undefined;
|
|
877
|
-
return {
|
|
878
|
-
successorKeyMultibase,
|
|
879
|
-
...(replacementHash !== undefined ? { replacementHash } : {})
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
/**
|
|
883
|
-
* The core of {@link credentialLadderAnchor}, over a pre-pass the caller
|
|
884
|
-
* already ran.
|
|
885
|
-
*
|
|
886
|
-
* @param options {object}
|
|
887
|
-
* @param options.log {DIDLog}
|
|
888
|
-
* @param options.facts {LadderEntryFacts[]} from {@link indexLadderLog}
|
|
889
|
-
* @param options.commitIndex {Map<string, LadderCommitOrigin>} likewise
|
|
890
|
-
* @param options.credentialVmId {string}
|
|
891
|
-
* @returns {Promise<{ anchorKeyMultibase?: string, anchorHash?: string } |
|
|
892
|
-
* undefined>}
|
|
893
|
-
*/
|
|
894
|
-
async function resolveBindAnchor({ log, facts, commitIndex, credentialVmId }) {
|
|
895
|
-
const did = credentialVmId.split('#')[0];
|
|
896
|
-
if (did === undefined || did === '') {
|
|
897
|
-
return undefined;
|
|
898
|
-
}
|
|
899
|
-
// Every update key the log attributes to a client the final document still
|
|
900
|
-
// lists, so the self-signed arm can refuse one outright. A client whose
|
|
901
|
-
// active key the log cannot attribute leaves that arm unable to refuse
|
|
902
|
-
// anything, so no anchor is named at all.
|
|
903
|
-
const enrolledClients = indexedEnrolledClients(log);
|
|
904
|
-
if (enrolledClients.some(client => client.updateKeyMultibase === undefined)) {
|
|
905
|
-
return undefined;
|
|
906
|
-
}
|
|
907
|
-
const enrolledClientKeys = new Set(enrolledClients
|
|
908
|
-
.map(client => client.updateKeyMultibase)
|
|
909
|
-
.filter((key) => key !== undefined));
|
|
910
|
-
let prevDoc;
|
|
911
|
-
for (const [index, entry] of log.entries()) {
|
|
912
|
-
const doc = entry.state;
|
|
913
|
-
if (doc === undefined) {
|
|
914
|
-
continue;
|
|
915
|
-
}
|
|
916
|
-
const introduced = introducedCredentialKeys({ doc, prevDoc, did });
|
|
917
|
-
const prevDocBefore = prevDoc;
|
|
918
|
-
prevDoc = doc;
|
|
919
|
-
if (!introduced.includes(credentialVmId)) {
|
|
920
|
-
continue;
|
|
921
|
-
}
|
|
922
|
-
// The bind entry.
|
|
923
|
-
const bind = facts[index];
|
|
924
|
-
if (bind === undefined) {
|
|
925
|
-
return undefined;
|
|
926
|
-
}
|
|
927
|
-
const publishesClient = introducesEnrolledClient({
|
|
928
|
-
doc: doc,
|
|
929
|
-
prevDoc: prevDocBefore,
|
|
930
|
-
did
|
|
931
|
-
});
|
|
932
|
-
// The recovery add-and-retire arm, on the two shapes the continuations
|
|
933
|
-
// write: two credential-class members and no client (the transient
|
|
934
|
-
// continuation), or one member beside a client (the remembered one). The
|
|
935
|
-
// successor key is an anchor only on the first shape, and only when the
|
|
936
|
-
// log attributes it to no enrolled client; on the second it is the
|
|
937
|
-
// client's update key, and only the replacement's hash is read.
|
|
938
|
-
const successorKey = bind.addedKeys[0];
|
|
939
|
-
const handover = successorKey === undefined || introduced.length > 2
|
|
940
|
-
? undefined
|
|
941
|
-
: resolveHandoverShape({
|
|
942
|
-
facts,
|
|
943
|
-
commitIndex,
|
|
944
|
-
bind,
|
|
945
|
-
index,
|
|
946
|
-
successorKeyMultibase: successorKey,
|
|
947
|
-
successorHash: await deriveNextKeyHash(successorKey),
|
|
948
|
-
retiresCredentialMember: retiredCredentialKeys({ doc, prevDoc: prevDocBefore, did })
|
|
949
|
-
.length > 0
|
|
950
|
-
});
|
|
951
|
-
if (handover !== undefined) {
|
|
952
|
-
const replacementAnchor = handover.replacementHash === undefined
|
|
953
|
-
? undefined
|
|
954
|
-
: { anchorHash: handover.replacementHash };
|
|
955
|
-
if (introduced.length === 2 && !publishesClient) {
|
|
956
|
-
if (credentialVmId === introduced[0]) {
|
|
957
|
-
return enrolledClientKeys.has(handover.successorKeyMultibase)
|
|
958
|
-
? undefined
|
|
959
|
-
: { anchorKeyMultibase: handover.successorKeyMultibase };
|
|
960
|
-
}
|
|
961
|
-
return replacementAnchor;
|
|
962
|
-
}
|
|
963
|
-
if (introduced.length === 1 && publishesClient) {
|
|
964
|
-
return replacementAnchor;
|
|
965
|
-
}
|
|
966
|
-
// A handover whose members match neither continuation's shape. The
|
|
967
|
-
// continuation refuses to re-bind a standing credential's own member
|
|
968
|
-
// (`RecoveryCredentialStandingError`), so no emitter writes a
|
|
969
|
-
// one-member transient entry; a log that carries one is refused here
|
|
970
|
-
// rather than have the self-signed-key arm below anchor the
|
|
971
|
-
// replacement on the fresh credential's rung 0.
|
|
972
|
-
return undefined;
|
|
973
|
-
}
|
|
974
|
-
// More than one credential-class member introduced here and nothing
|
|
975
|
-
// below can say which addition is whose.
|
|
976
|
-
if (introduced.length !== 1) {
|
|
977
|
-
return undefined;
|
|
978
|
-
}
|
|
979
|
-
// The fourth condition: an entry that also publishes an enrolled client
|
|
980
|
-
// names no credential's rung. The remembered recovery's add-and-retire
|
|
981
|
-
// entry is this shape -- the new client's key-agreement method is
|
|
982
|
-
// client-marked, so the credential-class count above sees only the
|
|
983
|
-
// replacement code and the ambiguity guard does not fire, while the one
|
|
984
|
-
// key the entry authorizes is the CLIENT's update key -- and the handover
|
|
985
|
-
// arm above is the only reading of it.
|
|
986
|
-
if (publishesClient) {
|
|
987
|
-
return undefined;
|
|
988
|
-
}
|
|
989
|
-
const revealed = bind.addedKeys[0];
|
|
990
|
-
if (bind.addedKeys.length === 1 &&
|
|
991
|
-
revealed !== undefined &&
|
|
992
|
-
bind.signers.includes(revealed) &&
|
|
993
|
-
// Belt and braces beside the condition above: never anchor on a key the
|
|
994
|
-
// log attributes to an enrolled client, whichever entry published it.
|
|
995
|
-
!enrolledClientKeys.has(revealed)) {
|
|
996
|
-
return { anchorKeyMultibase: revealed };
|
|
997
|
-
}
|
|
998
|
-
if (bind.addedKeys.length === 0 && bind.addedHashes.length === 1) {
|
|
999
|
-
// No enrolled-client check of its own: this arm reads a hash rather
|
|
1000
|
-
// than a key, and no ceremony fuses a credential bind with a client's
|
|
1001
|
-
// hash commitment.
|
|
1002
|
-
return { anchorHash: bind.addedHashes[0] };
|
|
1003
|
-
}
|
|
1004
|
-
return undefined;
|
|
1005
|
-
}
|
|
1006
|
-
return undefined;
|
|
1007
|
-
}
|
|
1008
915
|
/**
|
|
1009
916
|
* Whether one retiring credential's rung inventory can be claimed from the log
|
|
1010
|
-
* at all: its
|
|
917
|
+
* at all: its member must name an anchor, and the walk from that anchor
|
|
1011
918
|
* must not refuse. This is the log-only test, so it answers the same before
|
|
1012
919
|
* and after the retirement entry lands -- which is what lets a resumed run
|
|
1013
920
|
* report the same unclaimed set the first run reported.
|
|
@@ -1033,7 +940,7 @@ async function claimLadderInventory({ log, credentialVmId, maxScan }) {
|
|
|
1033
940
|
* lists, read off the log alone -- the latent commitments a client removal
|
|
1034
941
|
* must exclude before it attributes the removed client's staged hash. Each
|
|
1035
942
|
* credential-class `keyAgreement` member (an unmarked method, verbatim key or
|
|
1036
|
-
* commitment) is anchored from its
|
|
943
|
+
* commitment) is anchored from its member's `ladderCommitment` and walked
|
|
1037
944
|
* ({@link attributeLadderInventory}); a credential whose anchor or walk
|
|
1038
945
|
* refuses claims nothing and is named on `unclaimedCredentialVmIds`, so the
|
|
1039
946
|
* caller can tell "no latent hashes stand" from "one credential's could not
|
|
@@ -1147,9 +1054,12 @@ export async function retiredCredentialRungsBeforeKey({ log, authorizedKeyMultib
|
|
|
1147
1054
|
* than a property of the walk, because a mis-anchored walk landing on a
|
|
1148
1055
|
* client's key would otherwise end that client's ability to extend the
|
|
1149
1056
|
* account log for good. The walks therefore run FIRST, and the hashes they
|
|
1150
|
-
* claimed are passed to the protection as
|
|
1057
|
+
* claimed are passed to the protection as walk-derived, so a retiring
|
|
1151
1058
|
* credential's own rung cannot make a client's staged attribution ambiguous
|
|
1152
|
-
* and get itself protected as a candidate
|
|
1059
|
+
* and get itself protected as a candidate. The protection never lets such
|
|
1060
|
+
* a claim prune the hash the decision-0007 position names as a client's
|
|
1061
|
+
* staged hash, so a walk anchored on a member that names that hash claims
|
|
1062
|
+
* it and is withheld, rather than striking it;
|
|
1153
1063
|
* - a listed enrolled client whose ACTIVE update key the log cannot attribute
|
|
1154
1064
|
* withholds the WHOLE strike: nothing is struck and every credential is
|
|
1155
1065
|
* reported, since the structural guard cannot say what that client holds.
|
|
@@ -1195,7 +1105,7 @@ export async function attributeRetiredCredentialRungs({ log, credentialVmIds, pr
|
|
|
1195
1105
|
const surviving = await survivingClientKeyProtection({
|
|
1196
1106
|
log,
|
|
1197
1107
|
retiredVmIds: credentialVmIds,
|
|
1198
|
-
|
|
1108
|
+
derivedLatentHashes: [...claimedHashes]
|
|
1199
1109
|
});
|
|
1200
1110
|
if (surviving.ambiguous.length > 0) {
|
|
1201
1111
|
logger.warn('Withholding a credential rung strike: an enrolled client whose ' +
|
|
@@ -1340,29 +1250,31 @@ export async function attributeRetiredCredentialRungs({ log, credentialVmIds, pr
|
|
|
1340
1250
|
* once matches no legitimate history and fails closed ({@link
|
|
1341
1251
|
* LadderAttributionError}).
|
|
1342
1252
|
*
|
|
1343
|
-
* One shape is out of
|
|
1344
|
-
*
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1253
|
+
* One shape is out of the backward walk's reach. The last-client transition
|
|
1254
|
+
* strikes the ladder VM and reinstalls it in the same run
|
|
1255
|
+
* (`forgetLastEnrolledClient` stage 1): same seed, the credential's member
|
|
1256
|
+
* standing, the acting rung's hash still committed, and no hash added. A
|
|
1257
|
+
* later self-enrollment then spends that already-revealed rung, so its
|
|
1348
1258
|
* reveal-and-commit entry authorizes no key while committing the next rung's
|
|
1349
1259
|
* hash, and the registry anchor advances to that next rung. The backward walk
|
|
1350
1260
|
* climbs from the anchor by asking which key the entry that committed its
|
|
1351
1261
|
* hash authorized; that entry authorized none, so the walk cannot name the
|
|
1352
|
-
* rung that signed it, and
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1262
|
+
* rung that signed it, and a walk anchored on the registry key alone leaves
|
|
1263
|
+
* the earlier rung and the reinstalled VM unrecovered. A walk anchored on the
|
|
1264
|
+
* member's own rung-0 commitment reads that history forward with no climb,
|
|
1265
|
+
* which is why the removal paths walk from both anchors
|
|
1266
|
+
* (`attributeUnlockLadderInventory`) and act on the member's reading.
|
|
1355
1267
|
*
|
|
1356
1268
|
* The anchor comes in three forms, and the walk is the same afterwards. A
|
|
1357
1269
|
* recorded update-key multibase (`anchorKeyMultibase`) is what a caller
|
|
1358
1270
|
* holding a registry entry passes. A hash (`anchorHash`) is the same anchor
|
|
1359
1271
|
* with the key withheld: the rung is picked up when the log reveals it, since
|
|
1360
1272
|
* the reveal test already matches on the commitment. With neither, and a
|
|
1361
|
-
* `credentialVmId` in hand, the anchor is read off the credential's
|
|
1362
|
-
* ({@link credentialLadderAnchor}) -- the cold-browser
|
|
1363
|
-
* is readable before the entry is written.
|
|
1364
|
-
*
|
|
1365
|
-
* walking from a guess.
|
|
1273
|
+
* `credentialVmId` in hand, the anchor is read off the credential's own
|
|
1274
|
+
* `keyAgreement` member ({@link credentialLadderAnchor}) -- the cold-browser
|
|
1275
|
+
* mode, where no registry is readable before the entry is written. A member
|
|
1276
|
+
* naming no ladder commitment refuses with {@link LadderAttributionError}
|
|
1277
|
+
* rather than walking from a guess.
|
|
1366
1278
|
*
|
|
1367
1279
|
* @param options {object}
|
|
1368
1280
|
* @param options.log {DIDLog} a resolved, caller-verified log
|
|
@@ -1386,19 +1298,19 @@ export async function attributeLadderInventory({ log, anchorKeyMultibase, anchor
|
|
|
1386
1298
|
const { params, facts, commitIndex } = indexedLadderLog(log);
|
|
1387
1299
|
// The anchor, in the caller's order of preference: the recorded update key,
|
|
1388
1300
|
// a hash the caller resolved itself, or -- holding neither, the cold-browser
|
|
1389
|
-
// case -- the credential's own
|
|
1390
|
-
|
|
1301
|
+
// case -- the credential's own member's ladder commitment, read off the
|
|
1302
|
+
// log.
|
|
1303
|
+
const anchorKey = anchorKeyMultibase;
|
|
1391
1304
|
let anchorHash = suppliedAnchorHash;
|
|
1392
1305
|
if (anchorKey === undefined && anchorHash === undefined) {
|
|
1393
1306
|
const resolved = credentialVmId === undefined
|
|
1394
1307
|
? undefined
|
|
1395
|
-
:
|
|
1308
|
+
: credentialLadderAnchor({ log, credentialVmId });
|
|
1396
1309
|
if (resolved === undefined) {
|
|
1397
|
-
throw new LadderAttributionError('The ladder walk was given no anchor, and the log
|
|
1398
|
-
|
|
1399
|
-
'attribute an
|
|
1310
|
+
throw new LadderAttributionError('The ladder walk was given no anchor, and the log names no ladder ' +
|
|
1311
|
+
"commitment on this credential's keyAgreement member; refusing " +
|
|
1312
|
+
'to attribute an unanchored history.');
|
|
1400
1313
|
}
|
|
1401
|
-
anchorKey = resolved.anchorKeyMultibase;
|
|
1402
1314
|
anchorHash = resolved.anchorHash;
|
|
1403
1315
|
}
|
|
1404
1316
|
if (anchorHash === undefined) {
|
|
@@ -1540,8 +1452,15 @@ export async function attributeLadderInventory({ log, anchorKeyMultibase, anchor
|
|
|
1540
1452
|
if (reveals.length === 1) {
|
|
1541
1453
|
const key = reveals[0];
|
|
1542
1454
|
ladderKeys.add(key);
|
|
1543
|
-
|
|
1544
|
-
|
|
1455
|
+
// The same foreign-inventory narrowing the signed-while-revealed
|
|
1456
|
+
// branch below applies: a rung revealing itself in the entry that
|
|
1457
|
+
// binds another credential (the ladder-branch bind, a code's split
|
|
1458
|
+
// issuance) commits that credential's rung hash, not this ladder's
|
|
1459
|
+
// next one. Claiming it would strike the newcomer's commitment at this
|
|
1460
|
+
// credential's retirement, silently ending its update authority.
|
|
1461
|
+
const claims = installsForeignInventory ? [] : [...addedHashes];
|
|
1462
|
+
pending = { key, claims };
|
|
1463
|
+
for (const hash of claims) {
|
|
1545
1464
|
ladderHashes.add(hash);
|
|
1546
1465
|
}
|
|
1547
1466
|
// The HANDOVER: a rung whose hash some OTHER key committed earlier, in
|