OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-identity commits / 7aa771d

atproto-identity: extract as standalone repo (peer/bridge split, step 1)
Pure atproto identity layer (did:plc / secp256k1 / did:key / device attestation) extracted from atproto-bridge/identity per DECISION-2026-07-03-peer-bridge-split.md. A true leaf: lib + tests self-contained, no dependency on atproto-repo. Standalone postinstall bare-deps (copied into scripts/). Tests: 13/13, 53/53 asserts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7aa771d34f57035333ac0a7a83658ef6198c01a3
by plankatron <fleeky@gmail.com> · 2026-07-03T20:13:11.000Z

Changed files (18) +1917 -0

.gitignore +1 -0
……@@ -0,0 +1 @@
1node_modules/
lib/atproto-identity.js +98 -0
……@@ -0,0 +1,98 @@
1// Seed-anchored atproto identity — the secp256k1 / did:plc half.
2//
3// One BIP39 mnemonic anchors both worlds:
4// mnemonic -> Ed25519 (keet-identity-key) -> Hypercore log signing [NEXT increment]
5// mnemonic -> secp256k1 (this module) -> did:plc + atproto commits
6//
7// This module owns the atproto side: deterministic secp256k1 derivation, did:key
8// encoding, and building/signing a did:plc genesis operation — entirely offline.
9// It never submits to the PLC directory (the op-log is permanent; don't churn).
10
11import { mnemonicToSeedSync } from '@scure/bip39'
12import { HDKey } from '@scure/bip32'
13import { secp256k1 } from '@noble/curves/secp256k1'
14import { sha256 } from '@noble/hashes/sha2'
15import { base64url } from 'multiformats/bases/base64'
16import { encodeDidKey, CURVE_SECP256K1 } from './did-key.js'
17import { encodeOp, unsignedOp, didForCreateOp } from './did-plc.js'
18
19// Our fixed derivation path. No BIP44 coin type is registered for atproto, so this
20// is a project convention — the *mnemonic* is the recovery anchor; the path is fixed
21// by this constant so the same phrase always yields the same atproto key.
22export const ATPROTO_SIGNING_PATH = "m/44'/0'/0'/0/0"
23
24// raw base64url (no multibase prefix, no padding) — atproto's sig encoding.
25function sigToBase64url (bytes) {
26 return base64url.encode(bytes).slice(1) // drop multibase 'u'
27}
28export function base64urlToBytes (str) {
29 return base64url.decode('u' + str)
30}
31
32// mnemonic -> deterministic secp256k1 keypair for atproto signing.
33export function deriveAtprotoKey (mnemonic, path = ATPROTO_SIGNING_PATH) {
34 const seed = mnemonicToSeedSync(mnemonic)
35 const node = HDKey.fromMasterSeed(seed).derive(path)
36 if (!node.privateKey) throw new Error('derivation produced no private key')
37 return keyFromPrivate(node.privateKey)
38}
39
40// raw 32-byte secp256k1 private key -> { privateKey, publicKey, didKey }.
41export function keyFromPrivate (privateKey) {
42 const publicKey = secp256k1.getPublicKey(privateKey, true) // 33-byte compressed
43 return { privateKey, publicKey, didKey: encodeDidKey(CURVE_SECP256K1, publicKey) }
44}
45
46// A fresh random atproto signing keypair (server-minted accounts with no seed anchor).
47export function generateAtprotoKey () {
48 return keyFromPrivate(secp256k1.utils.randomPrivateKey())
49}
50
51// ECDSA sign the sha256 of DAG-CBOR(unsignedOp), low-S, compact -> base64url.
52export function signOp (unsigned, privateKey) {
53 const hash = sha256(encodeOp(unsigned))
54 const sig = secp256k1.sign(hash, privateKey, { lowS: true })
55 return sigToBase64url(sig.toCompactRawBytes())
56}
57
58// verify a signature string against an unsigned op + did:key-encoded pubkey bytes.
59export function verifyOp (unsigned, sigB64url, publicKey) {
60 const hash = sha256(encodeOp(unsigned))
61 return secp256k1.verify(base64urlToBytes(sigB64url), hash, publicKey, { lowS: true })
62}
63
64// Build an UNSIGNED did:plc genesis op. rotationKey defaults to the signing key
65// (single-key setup); pass a distinct rotation didKey for key-separation.
66export function buildGenesisOp ({ signingDidKey, rotationDidKeys, handle, pdsUrl }) {
67 return {
68 type: 'plc_operation',
69 rotationKeys: rotationDidKeys && rotationDidKeys.length ? rotationDidKeys : [signingDidKey],
70 verificationMethods: { atproto: signingDidKey },
71 alsoKnownAs: [`at://${handle}`],
72 services: {
73 atproto_pds: { type: 'AtprotoPersonalDataServer', endpoint: pdsUrl }
74 },
75 prev: null
76 }
77}
78
79// key { privateKey, publicKey, didKey } -> signed genesis op + did:plc.
80export function identityFromKey (key, { handle, pdsUrl }) {
81 const unsigned = buildGenesisOp({ signingDidKey: key.didKey, handle, pdsUrl })
82 const sig = signOp(unsigned, key.privateKey)
83 const signedOp = { ...unsigned, sig }
84 const did = didForCreateOp(signedOp)
85 return { did, didKey: key.didKey, publicKey: key.publicKey, privateKey: key.privateKey, signedOp }
86}
87
88// Full offline flow: mnemonic -> signed genesis op + did:plc.
89export function createAtprotoIdentity (mnemonic, { handle, pdsUrl, path = ATPROTO_SIGNING_PATH }) {
90 return identityFromKey(deriveAtprotoKey(mnemonic, path), { handle, pdsUrl })
91}
92
93// Same, but from a raw private key (server-minted / bring-your-own accounts).
94export function createAtprotoIdentityFromKey (privateKey, { handle, pdsUrl }) {
95 return identityFromKey(keyFromPrivate(privateKey), { handle, pdsUrl })
96}
97
98export { unsignedOp, didForCreateOp }
lib/attested-anchor.js +42 -0
……@@ -0,0 +1,42 @@
1// Compose the full device-attested atproto anchor — both worlds, from one root + one
2// device. See docs/ATPROTO-P2P-BRIDGE.md (device attestation) and §13 (purity firewall).
3//
4// atproto (federation-visible): genesis with rotationKeys=[root secp256k1] +
5// verificationMethod=[device secp256k1], signed by root.
6// keet (P2P-verifiable): root → device(Ed25519) → vouches-for(did).
7//
8// The ROOT box (holds rootMnemonic) signs the genesis + attests the device. The DEVICE box
9// only ever needs `deviceSigningKey` (secp256k1) to run the PDS. In production the device
10// derives its own keys and sends only PUBLIC keys to the root — use buildAttestedIdentity
11// with `deviceDidKey` for that path; this helper takes both mnemonics for the end-to-end
12// (dev / single-operator) case.
13
14import { buildAttestedIdentity } from './device-attestation.js'
15import { deviceKeetKeyPair, attestDeviceChain, attestAtprotoBinding } from './device-chain.js'
16
17export async function buildAttestedAnchor ({
18 rootMnemonic, rootSeed,
19 deviceMnemonic, deviceSeed, devicePath,
20 handle, pdsUrl,
21 extraRotationDidKeys = []
22}) {
23 // atproto side — the permanent, PLC-bound genesis
24 const atp = buildAttestedIdentity({ rootMnemonic, deviceMnemonic, devicePath, handle, pdsUrl, extraRotationDidKeys })
25
26 // keet side — the P2P attestation binding the DID to the root identity
27 const deviceKeet = await deviceKeetKeyPair(deviceMnemonic, deviceSeed)
28 const chain = await attestDeviceChain({ rootMnemonic, rootSeed, devicePublicKey: deviceKeet.publicKey })
29 const keetProof = attestAtprotoBinding({ deviceKeyPair: deviceKeet, did: atp.did, proof: chain })
30
31 return {
32 did: atp.did,
33 handle,
34 pdsUrl,
35 signedOp: atp.signedOp, // → the one irreversible PLC write publishes this
36 rootDidKey: atp.rootDidKey, // secp256k1 rotation (authority/recovery — root box)
37 deviceDidKey: atp.deviceDidKey, // secp256k1 signing (verificationMethod — device box)
38 deviceSigningKey: atp.deviceSigningKey, // { privateKey, … } — all the PDS needs to sign commits
39 deviceKeetPublicKey: deviceKeet.publicKey, // Ed25519 device identity (P2P)
40 keetProof // P2P-verifiable: chain resolves to root, device vouches for did
41 }
42}
lib/binding.js +47 -0
……@@ -0,0 +1,47 @@
1// The identity binding — one BIP39 mnemonic, both worlds.
2//
3// mnemonic ─┬─ keet-identity-key ──► Ed25519 identity key (Hypercore / our P2P)
4// └─ this repo ──► secp256k1 + did:plc (atproto / bsky)
5//
6// Verified prerequisite: keet's bip39-mnemonic and our @scure/bip39 derive the
7// IDENTICAL standard BIP39 seed, so the two derivations agree on one phrase (see
8// the seed-equality check). This module produces the durable binding record that
9// hyper-identity's `atproto` provider persists.
10
11import IdentityKey from 'keet-identity-key'
12import { createAtprotoIdentity, deriveAtprotoKey, ATPROTO_SIGNING_PATH } from './atproto-identity.js'
13
14function toHex (bytes) { return Buffer.from(bytes).toString('hex') }
15
16// mnemonic -> { keet Ed25519 identity pubkey, atproto secp256k1/did:plc }.
17export async function deriveIdentity (mnemonic, { handle, pdsUrl, path = ATPROTO_SIGNING_PATH }) {
18 const idk = await IdentityKey.from({ mnemonic })
19 const ed25519PublicKey = Buffer.from(idk.identityPublicKey) // copy before clear()
20 idk.clear() // zero the secret material we don't retain
21 const atproto = createAtprotoIdentity(mnemonic, { handle, pdsUrl, path })
22 return {
23 ed25519PublicKey, // keet identity (Hypercore world)
24 secp256k1PublicKey: atproto.publicKey, // atproto signing (bsky world)
25 didKey: atproto.didKey, // did:key:zQ3s…
26 did: atproto.did, // did:plc:…
27 handle,
28 pdsUrl,
29 signedOp: atproto.signedOp
30 }
31}
32
33// JSON-safe binding record for the hyper-identity `atproto` provider / registry.
34export async function bindingRecord (mnemonic, opts) {
35 const id = await deriveIdentity(mnemonic, opts)
36 return {
37 provider: 'atproto',
38 did: id.did,
39 handle: id.handle,
40 pdsUrl: id.pdsUrl,
41 didKey: id.didKey,
42 ed25519PublicKey: toHex(id.ed25519PublicKey),
43 secp256k1PublicKey: toHex(id.secp256k1PublicKey)
44 }
45}
46
47export { deriveAtprotoKey }
lib/device-attestation.js +76 -0
……@@ -0,0 +1,76 @@
1// Device-attested atproto anchoring.
2//
3// The atproto identity separates two roles (this is atproto's native design, and it maps
4// onto a device chain — see docs/ATPROTO-P2P-BRIDGE.md §device-attestation):
5//
6// rotationKeys = authority / recovery keys (the ROOT identity, kept off the
7// serving box). Any rotationKey can sign a PLC op to rotate the
8// signing key, change the handle, or move the PDS.
9// verificationMethod = the repo SIGNING key (a per-DEVICE key — the box that runs the
10// PDS holds only this). Signs every commit.
11//
12// So the genesis op is signed by the ROOT rotation key and authorizes a DEVICE signing
13// key. The device never holds the root seed; if the device is lost, the root rotates to a
14// new device and the DID/handle/repos persist. did:plc = hash(genesis), so the rotation
15// set is baked at genesis — this MUST be right before the one irreversible PLC write.
16//
17// This module owns the atproto (secp256k1 / PLC) side. The parallel keet-identity-key
18// Ed25519 device chain (our P2P-verifiable attestation) lives in device-chain.js; the two
19// run in parallel off the same device, bound by device-chain.js's attestData over the DID.
20
21import { deriveAtprotoKey, keyFromPrivate, buildGenesisOp, signOp, verifyOp } from './atproto-identity.js'
22import { unsignedOp, didForCreateOp } from './did-plc.js'
23
24// Resolve a { privateKey, publicKey, didKey } from a mnemonic or a raw secp256k1 key.
25function resolveKey (mnemonic, key, path) {
26 if (key) return keyFromPrivate(key)
27 if (mnemonic) return deriveAtprotoKey(mnemonic, path)
28 throw new Error('device-attestation: need a mnemonic or a key')
29}
30
31// Build a device-attested did:plc:
32// rotationKeys = [root, ...extraRotationDidKeys] (root signs the genesis)
33// verificationMethod = the device signing key
34// Pass root by { rootMnemonic } or { rootKey } (secp256k1 private); device by
35// { deviceMnemonic, devicePath } or { deviceKey }. Optionally list additional rotation
36// did:keys (e.g. the device itself, or a second recovery device) for a real key chain.
37export function buildAttestedIdentity ({
38 rootMnemonic, rootKey,
39 deviceMnemonic, deviceKey, deviceDidKey, devicePath,
40 handle, pdsUrl,
41 extraRotationDidKeys = []
42}) {
43 const root = resolveKey(rootMnemonic, rootKey)
44
45 // Device: the REAL flow passes only the device's PUBLIC did:key — the device private key
46 // never leaves the device box, and the root just authorizes the pubkey into the genesis.
47 // The dev/test flow may pass a device mnemonic/key so the full signing key is available.
48 let deviceSigningKey = null
49 let deviceKeyId = deviceDidKey
50 if (!deviceKeyId) {
51 deviceSigningKey = resolveKey(deviceMnemonic, deviceKey, devicePath)
52 deviceKeyId = deviceSigningKey.didKey
53 }
54
55 const rotationDidKeys = [root.didKey, ...extraRotationDidKeys]
56 const unsigned = buildGenesisOp({ signingDidKey: deviceKeyId, rotationDidKeys, handle, pdsUrl })
57 const sig = signOp(unsigned, root.privateKey) // signed by the ROOT rotation key
58 const signedOp = { ...unsigned, sig }
59 const did = didForCreateOp(signedOp)
60
61 return {
62 did,
63 signedOp,
64 rootDidKey: root.didKey,
65 rootPublicKey: root.publicKey,
66 deviceDidKey: deviceKeyId,
67 deviceSigningKey, // { privateKey, publicKey, didKey } when available; null in the pubkey-only root flow
68 rotationKeys: rotationDidKeys
69 }
70}
71
72// Verify an attested genesis: it must be signed by (one of) its rotation keys — i.e. by
73// the ROOT, not the device. Pass the root's secp256k1 public key.
74export function verifyAttestedGenesis (signedOp, rootPublicKey) {
75 return verifyOp(unsignedOp(signedOp), signedOp.sig, rootPublicKey)
76}
lib/device-chain.js +45 -0
……@@ -0,0 +1,45 @@
1// keet-identity-key device chain — the P2P-verifiable half of device attestation.
2//
3// Runs in PARALLEL to the atproto PLC authority (device-attestation.js). atproto's
4// rotationKeys/PLC-op-log carry the FEDERATION-visible authority (secp256k1); this keet
5// chain carries the authority OUR P2P world verifies (Ed25519). Both anchor on the same
6// root identity and vouch for the same device.
7//
8// Shape (from keet-identity-key): a proof is `{ identity, epoch, chain: [{publicKey,
9// signature}], data? }`. `bootstrap` attests a device pubkey under the root identity;
10// `attestData` lets the (leaf) device sign arbitrary data — here, the atproto DID — so a
11// verifier learns "root → device → this device vouches for did:plc:…". `verify` walks the
12// chain back to the identity and checks the data signature.
13
14import IdentityKey from 'keet-identity-key'
15import b4a from 'b4a'
16
17// The device's keet-world Ed25519 keypair (derived from the device seed) — distinct from
18// its secp256k1 atproto signing key. This is the device's identity in the keet chain.
19export async function deviceKeetKeyPair (mnemonic, seed) {
20 const idk = await IdentityKey.from({ mnemonic, seed })
21 return idk.identityKeyPair // { publicKey, secretKey }
22}
23
24// The root identity's Ed25519 public key — the anchor every chain must resolve to.
25export async function rootIdentityPublicKey (mnemonic, seed) {
26 const idk = await IdentityKey.from({ mnemonic, seed })
27 return b4a.from(idk.identityPublicKey)
28}
29
30// Root identity attests a device's Ed25519 public key → an encoded proof chain.
31export async function attestDeviceChain ({ rootMnemonic, rootSeed, devicePublicKey }) {
32 return IdentityKey.bootstrap({ mnemonic: rootMnemonic, seed: rootSeed }, devicePublicKey)
33}
34
35// The attested (leaf) device vouches for the atproto DID by signing it as attested data,
36// extending the proof with `proof.data`. Result: root → device → vouches-for(did).
37export function attestAtprotoBinding ({ deviceKeyPair, did, proof }) {
38 return IdentityKey.attestData(b4a.from(did), deviceKeyPair, proof)
39}
40
41// Verify the full binding: the chain resolves to the root identity AND the device vouched
42// for exactly this DID. Returns { receipt, identityPublicKey, devicePublicKey } or null.
43export function verifyAtprotoBinding ({ proof, did, expectedIdentity, expectedDevice }) {
44 return IdentityKey.verify(proof, b4a.from(did), { expectedIdentity, expectedDevice })
45}
lib/did-key.js +61 -0
……@@ -0,0 +1,61 @@
1// did:key encoding/decoding for the two curves atproto accepts.
2//
3// did:key:z<base58btc( <multicodec-varint> ++ <compressed-pubkey> )>
4//
5// Multicodec key-type prefixes (unsigned-varint):
6// secp256k1-pub = 0xe7 -> varint [0xe7, 0x01] -> did:key:zQ3s...
7// p256-pub = 0x1200 -> varint [0x80, 0x24] -> did:key:zDna...
8//
9// The pubkey is the 33-byte SEC1 compressed point (0x02/0x03 ++ x). This matches
10// @atproto/crypto's formatDidKey. Validated by the P-256 golden round-trip test.
11
12import { base58btc } from 'multiformats/bases/base58'
13
14export const CURVE_SECP256K1 = 'secp256k1'
15export const CURVE_P256 = 'p256'
16
17const MULTICODEC = {
18 [CURVE_SECP256K1]: 0xe7,
19 [CURVE_P256]: 0x1200
20}
21const BY_CODE = { 0xe7: CURVE_SECP256K1, 0x1200: CURVE_P256 }
22
23function varintEncode (n) {
24 const out = []
25 while (n >= 0x80) { out.push((n & 0x7f) | 0x80); n >>>= 7 }
26 out.push(n)
27 return Uint8Array.from(out)
28}
29
30function varintDecode (bytes) {
31 let n = 0, shift = 0, i = 0
32 for (;;) {
33 const b = bytes[i++]
34 n |= (b & 0x7f) << shift
35 if ((b & 0x80) === 0) break
36 shift += 7
37 }
38 return { code: n >>> 0, length: i }
39}
40
41// compressed pubkey bytes (33) -> did:key string
42export function encodeDidKey (curve, compressedPubkey) {
43 const code = MULTICODEC[curve]
44 if (code === undefined) throw new Error(`unsupported curve: ${curve}`)
45 const prefix = varintEncode(code)
46 const bytes = new Uint8Array(prefix.length + compressedPubkey.length)
47 bytes.set(prefix, 0)
48 bytes.set(compressedPubkey, prefix.length)
49 return `did:key:${base58btc.encode(bytes)}` // base58btc.encode already prepends 'z'
50}
51
52// did:key string -> { curve, pubkey (compressed bytes) }
53export function decodeDidKey (didKey) {
54 if (!didKey.startsWith('did:key:')) throw new Error('not a did:key')
55 const mb = didKey.slice('did:key:'.length)
56 const bytes = base58btc.decode(mb) // expects leading 'z'
57 const { code, length } = varintDecode(bytes)
58 const curve = BY_CODE[code]
59 if (!curve) throw new Error(`unknown multicodec 0x${code.toString(16)}`)
60 return { curve, pubkey: bytes.slice(length) }
61}
lib/did-plc.js +43 -0
……@@ -0,0 +1,43 @@
1// did:plc computation — byte-exact.
2//
3// A did:plc is derived from its *genesis* operation:
4// did = "did:plc:" + base32( sha256( dag-cbor(signedGenesisOp) ) ).slice(0, 24)
5//
6// The hash is over the SIGNED op (the `sig` field is included). DAG-CBOR gives a
7// canonical byte layout (sorted map keys, definite lengths), so the DID is a pure
8// function of the op. base32 here is RFC-4648 lowercase, NO padding, and NO
9// multibase prefix (did-method-plc uses raw base32, not multiformats' 'b'-prefixed
10// multibase form) — see the golden-vector test.
11//
12// Reference: https://github.com/did-method-plc/did-method-plc (didForCreateOp)
13
14import * as dagCbor from '@ipld/dag-cbor'
15import { sha256 } from '@noble/hashes/sha2'
16import { base32 } from 'multiformats/bases/base32'
17
18// multiformats' base32.encode returns a multibase string prefixed with 'b'.
19// did:plc uses the raw base32 alphabet output, so strip the leading multibase char.
20function rawBase32 (bytes) {
21 const multibase = base32.encode(bytes) // 'b' + <rfc4648-lower-nopad>
22 return multibase.slice(1)
23}
24
25// Canonical DAG-CBOR bytes for an operation object.
26export function encodeOp (op) {
27 return dagCbor.encode(op)
28}
29
30// Compute the did:plc for a signed genesis operation.
31export function didForCreateOp (signedOp, truncate = 24) {
32 const bytes = encodeOp(signedOp)
33 const hash = sha256(bytes)
34 const b32 = rawBase32(hash)
35 return `did:plc:${b32.slice(0, truncate)}`
36}
37
38// The unsigned operation is everything except `sig`. did:plc signs the DAG-CBOR of
39// the unsigned op; the signature is then attached and the DID hashes the signed form.
40export function unsignedOp (op) {
41 const { sig, ...rest } = op // eslint-disable-line no-unused-vars
42 return rest
43}
package-lock.json +1146 -0
……@@ -0,0 +1,1146 @@
1{
2 "name": "atproto-identity",
3 "version": "0.0.1",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "atproto-identity",
9 "version": "0.0.1",
10 "hasInstallScript": true,
11 "dependencies": {
12 "@ipld/dag-cbor": "^9.2.1",
13 "@noble/curves": "^1.6.0",
14 "@noble/hashes": "^1.5.0",
15 "@scure/bip32": "^1.5.0",
16 "@scure/bip39": "^1.4.0",
17 "keet-identity-key": "^3.2.0",
18 "multiformats": "^13.3.0"
19 },
20 "devDependencies": {
21 "brittle": "^4.0.2"
22 }
23 },
24 "node_modules/@ipld/dag-cbor": {
25 "version": "9.2.7",
26 "resolved": "https://registry.npmjs.org/@ipld/dag-cbor/-/dag-cbor-9.2.7.tgz",
27 "integrity": "sha512-ZmfXmElRWATr+hoUTSAOr6HUcjVhOcNHDqgczc76qte2DHHFEK0ZhNzUcdTDQhF/VSIvf2ioaRTRLWwLc83sNw==",
28 "license": "Apache-2.0 OR MIT",
29 "dependencies": {
30 "cborg": "^5.0.1",
31 "multiformats": "^13.1.0"
32 }
33 },
34 "node_modules/@jridgewell/resolve-uri": {
35 "version": "3.1.2",
36 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
37 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
38 "dev": true,
39 "license": "MIT",
40 "engines": {
41 "node": ">=6.0.0"
42 }
43 },
44 "node_modules/@jridgewell/sourcemap-codec": {
45 "version": "1.5.5",
46 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
47 "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
48 "dev": true,
49 "license": "MIT"
50 },
51 "node_modules/@jridgewell/trace-mapping": {
52 "version": "0.3.31",
53 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
54 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
55 "dev": true,
56 "license": "MIT",
57 "dependencies": {
58 "@jridgewell/resolve-uri": "^3.1.0",
59 "@jridgewell/sourcemap-codec": "^1.4.14"
60 }
61 },
62 "node_modules/@noble/curves": {
63 "version": "1.9.7",
64 "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz",
65 "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==",
66 "license": "MIT",
67 "dependencies": {
68 "@noble/hashes": "1.8.0"
69 },
70 "engines": {
71 "node": "^14.21.3 || >=16"
72 },
73 "funding": {
74 "url": "https://paulmillr.com/funding/"
75 }
76 },
77 "node_modules/@noble/hashes": {
78 "version": "1.8.0",
79 "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
80 "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
81 "license": "MIT",
82 "engines": {
83 "node": "^14.21.3 || >=16"
84 },
85 "funding": {
86 "url": "https://paulmillr.com/funding/"
87 }
88 },
89 "node_modules/@scure/base": {
90 "version": "1.2.6",
91 "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz",
92 "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==",
93 "license": "MIT",
94 "funding": {
95 "url": "https://paulmillr.com/funding/"
96 }
97 },
98 "node_modules/@scure/bip32": {
99 "version": "1.7.0",
100 "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz",
101 "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==",
102 "license": "MIT",
103 "dependencies": {
104 "@noble/curves": "~1.9.0",
105 "@noble/hashes": "~1.8.0",
106 "@scure/base": "~1.2.5"
107 },
108 "funding": {
109 "url": "https://paulmillr.com/funding/"
110 }
111 },
112 "node_modules/@scure/bip39": {
113 "version": "1.6.0",
114 "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz",
115 "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==",
116 "license": "MIT",
117 "dependencies": {
118 "@noble/hashes": "~1.8.0",
119 "@scure/base": "~1.2.5"
120 },
121 "funding": {
122 "url": "https://paulmillr.com/funding/"
123 }
124 },
125 "node_modules/@types/istanbul-lib-coverage": {
126 "version": "2.0.6",
127 "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
128 "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
129 "dev": true,
130 "license": "MIT"
131 },
132 "node_modules/b4a": {
133 "version": "1.8.1",
134 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
135 "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
136 "license": "Apache-2.0",
137 "peerDependencies": {
138 "react-native-b4a": "*"
139 },
140 "peerDependenciesMeta": {
141 "react-native-b4a": {
142 "optional": true
143 }
144 }
145 },
146 "node_modules/bare-abort": {
147 "version": "2.0.13",
148 "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz",
149 "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==",
150 "dev": true,
151 "license": "Apache-2.0"
152 },
153 "node_modules/bare-addon-resolve": {
154 "version": "1.10.0",
155 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz",
156 "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==",
157 "license": "Apache-2.0",
158 "dependencies": {
159 "bare-module-resolve": "^1.10.0",
160 "bare-semver": "^1.0.0"
161 },
162 "peerDependencies": {
163 "bare-url": "*"
164 },
165 "peerDependenciesMeta": {
166 "bare-url": {
167 "optional": true
168 }
169 }
170 },
171 "node_modules/bare-ansi-escapes": {
172 "version": "2.2.3",
173 "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz",
174 "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==",
175 "license": "Apache-2.0",
176 "dependencies": {
177 "bare-stream": "^2.6.5"
178 },
179 "peerDependencies": {
180 "bare-buffer": "*"
181 },
182 "peerDependenciesMeta": {
183 "bare-buffer": {
184 "optional": true
185 }
186 }
187 },
188 "node_modules/bare-assert": {
189 "version": "1.2.0",
190 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
191 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
192 "license": "Apache-2.0",
193 "dependencies": {
194 "bare-inspect": "^3.1.2"
195 }
196 },
197 "node_modules/bare-buffer": {
198 "version": "3.6.1",
199 "resolved": "https://registry.npmjs.org/bare-buffer/-/bare-buffer-3.6.1.tgz",
200 "integrity": "sha512-8mzp60t6jdSD3cEGmyxAV1YrAN2+mnxiUvBPQHJJ4WNk0hSVLJGSIEolRj09ZP8yt/+oSj2N2f5kDVW8297n4A==",
201 "devOptional": true,
202 "license": "Apache-2.0",
203 "engines": {
204 "bare": ">=1.20.0"
205 }
206 },
207 "node_modules/bare-bundle": {
208 "version": "1.10.0",
209 "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz",
210 "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==",
211 "dev": true,
212 "license": "Apache-2.0",
213 "peerDependencies": {
214 "bare-buffer": "*",
215 "bare-url": "*"
216 },
217 "peerDependenciesMeta": {
218 "bare-buffer": {
219 "optional": true
220 },
221 "bare-url": {
222 "optional": true
223 }
224 }
225 },
226 "node_modules/bare-channel": {
227 "version": "5.2.4",
228 "resolved": "https://registry.npmjs.org/bare-channel/-/bare-channel-5.2.4.tgz",
229 "integrity": "sha512-enkaOtvXUiZsLBSbataxV/QBjehCOK+SUTd2JQWUYW2iIOufpPu9jyZ9bqJqkEquktrK/rpEMVEmKsPzoci/sA==",
230 "dev": true,
231 "license": "Apache-2.0",
232 "dependencies": {
233 "bare-events": "^2.0.0",
234 "bare-stream": "^2.7.0",
235 "bare-structured-clone": "^1.4.0"
236 },
237 "engines": {
238 "bare": ">=1.7.0"
239 }
240 },
241 "node_modules/bare-cov": {
242 "version": "1.2.2",
243 "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.2.tgz",
244 "integrity": "sha512-d/b4HdL5ohZDwBvDegJeSkmo1X2MGlm22jXzuY2D7wl2W1+7nk/b7+HMogwgUa+zi8rVVRJQKHCHPHYBJpvlvw==",
245 "dev": true,
246 "license": "Apache-2.0",
247 "dependencies": {
248 "bare-fs": "^4.1.2",
249 "bare-inspector": "^6.0.1",
250 "bare-path": "^3.0.0",
251 "bare-process": "^4.2.1",
252 "bare-url": "^2.1.5",
253 "bare-v8-to-istanbul": "^1.0.2",
254 "picomatch": "^4.0.2",
255 "which-runtime": "^1.2.1"
256 }
257 },
258 "node_modules/bare-crypto": {
259 "version": "1.15.3",
260 "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.15.3.tgz",
261 "integrity": "sha512-macV9lbyJTsLPRXJkBtz8ivTGEo3LCyJInLT9IB/PWJ7pRXwvHs/FP4bx/fWw+HZkiepIYCAV2cuU5CR92XWCw==",
262 "dev": true,
263 "license": "Apache-2.0",
264 "dependencies": {
265 "bare-assert": "^1.2.0",
266 "bare-stream": "^2.6.3"
267 },
268 "peerDependencies": {
269 "bare-buffer": "*"
270 },
271 "peerDependenciesMeta": {
272 "bare-buffer": {
273 "optional": true
274 }
275 }
276 },
277 "node_modules/bare-debug-log": {
278 "version": "2.0.0",
279 "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz",
280 "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==",
281 "dev": true,
282 "license": "Apache-2.0",
283 "dependencies": {
284 "bare-os": "^3.0.1"
285 }
286 },
287 "node_modules/bare-dns": {
288 "version": "2.1.4",
289 "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz",
290 "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==",
291 "dev": true,
292 "license": "Apache-2.0",
293 "engines": {
294 "bare": ">=1.7.0"
295 }
296 },
297 "node_modules/bare-encoding": {
298 "version": "1.0.3",
299 "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz",
300 "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==",
301 "dev": true,
302 "license": "Apache-2.0",
303 "peerDependencies": {
304 "bare-buffer": "*"
305 },
306 "peerDependenciesMeta": {
307 "bare-buffer": {
308 "optional": true
309 }
310 }
311 },
312 "node_modules/bare-env": {
313 "version": "3.0.0",
314 "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz",
315 "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==",
316 "dev": true,
317 "license": "Apache-2.0",
318 "dependencies": {
319 "bare-os": "^3.0.1"
320 }
321 },
322 "node_modules/bare-events": {
323 "version": "2.9.1",
324 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
325 "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
326 "license": "Apache-2.0",
327 "peerDependencies": {
328 "bare-abort-controller": "*"
329 },
330 "peerDependenciesMeta": {
331 "bare-abort-controller": {
332 "optional": true
333 }
334 }
335 },
336 "node_modules/bare-format": {
337 "version": "1.0.2",
338 "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz",
339 "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==",
340 "dev": true,
341 "license": "Apache-2.0",
342 "dependencies": {
343 "bare-inspect": "^3.0.0"
344 }
345 },
346 "node_modules/bare-fs": {
347 "version": "4.7.3",
348 "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.3.tgz",
349 "integrity": "sha512-xRgplks8SvcKkdlv2M6Z2LZmRsmqd+x0nXXGXeMEjwdibj1HSDrlnqBRLeYdMvsgCox7Bq0e+DHwfczOfsn6IA==",
350 "dev": true,
351 "license": "Apache-2.0",
352 "dependencies": {
353 "bare-events": "^2.5.4",
354 "bare-path": "^3.0.0",
355 "bare-stream": "^2.6.4",
356 "bare-url": "^2.2.2",
357 "fast-fifo": "^1.3.2"
358 },
359 "engines": {
360 "bare": ">=1.16.0"
361 },
362 "peerDependencies": {
363 "bare-buffer": "*"
364 },
365 "peerDependenciesMeta": {
366 "bare-buffer": {
367 "optional": true
368 }
369 }
370 },
371 "node_modules/bare-hrtime": {
372 "version": "2.1.1",
373 "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz",
374 "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==",
375 "dev": true,
376 "license": "Apache-2.0"
377 },
378 "node_modules/bare-http-parser": {
379 "version": "1.1.5",
380 "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.5.tgz",
381 "integrity": "sha512-sPaDetLbWRmth04JmuTCvw/hoNdWJvYUJN8n1tYdGW3HM0mMnCvK2f0oIxE6HK7iOq+WlsRzEMg1LT/b0wGbLQ==",
382 "dev": true,
383 "license": "Apache-2.0"
384 },
385 "node_modules/bare-http1": {
386 "version": "4.5.7",
387 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.7.tgz",
388 "integrity": "sha512-PRuzs9ywt4vUvrC3mnHhQyaQfLYzdFy8XKOH0oKeKmYfyYYUqXBkdbJE2csESLBxJEbKDBjxPGLU+Qa0l1ds4A==",
389 "dev": true,
390 "license": "Apache-2.0",
391 "dependencies": {
392 "bare-events": "^2.6.0",
393 "bare-http-parser": "^1.1.1",
394 "bare-stream": "^2.10.0",
395 "bare-tcp": "^2.2.0"
396 },
397 "peerDependencies": {
398 "bare-buffer": "*",
399 "bare-url": "*"
400 },
401 "peerDependenciesMeta": {
402 "bare-buffer": {
403 "optional": true
404 },
405 "bare-url": {
406 "optional": true
407 }
408 }
409 },
410 "node_modules/bare-https": {
411 "version": "3.0.0",
412 "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-3.0.0.tgz",
413 "integrity": "sha512-W1GRSCzn+xXKf5bMcPs/hg6Ga1bxPqb7owGfS+tvlBQfPe5Q2STcanRuKZrgU60v5uKrhXH5cgWwM+DLqvXZgQ==",
414 "dev": true,
415 "license": "Apache-2.0",
416 "dependencies": {
417 "bare-http1": "^4.4.0",
418 "bare-tcp": "^2.2.0",
419 "bare-tls": "^3.0.0"
420 }
421 },
422 "node_modules/bare-inspect": {
423 "version": "3.1.4",
424 "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz",
425 "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==",
426 "license": "Apache-2.0",
427 "dependencies": {
428 "bare-ansi-escapes": "^2.1.0",
429 "bare-type": "^1.0.0"
430 },
431 "engines": {
432 "bare": ">=1.18.0"
433 }
434 },
435 "node_modules/bare-inspector": {
436 "version": "6.1.0",
437 "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-6.1.0.tgz",
438 "integrity": "sha512-PRxmZ4gF+K3TLzGubgRFvzdECybTCSKackgNsAdd4e7SdvICxMkGj+p5iX7bSKYSmgDnxgCR+2Z6UXmWykKhvw==",
439 "dev": true,
440 "license": "Apache-2.0",
441 "dependencies": {
442 "bare-events": "^2.1.0",
443 "bare-http1": "^4.0.0",
444 "bare-stream": "^2.0.0",
445 "bare-url": "^2.0.0",
446 "bare-ws": "^3.0.0"
447 },
448 "engines": {
449 "bare": ">=1.29.0"
450 },
451 "peerDependencies": {
452 "bare-tcp": "*"
453 },
454 "peerDependenciesMeta": {
455 "bare-tcp": {
456 "optional": true
457 }
458 }
459 },
460 "node_modules/bare-module": {
461 "version": "6.4.0",
462 "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.4.0.tgz",
463 "integrity": "sha512-Yn4V5g5EqGQL4LYUOmt7fjKzj2JPWyJOqE3lPoeZwfUH5rk4CKUfZj6JhDwbzhBYCqqmUgjgQ5aY8cihAPILLA==",
464 "dev": true,
465 "license": "Apache-2.0",
466 "dependencies": {
467 "bare-bundle": "^1.3.0",
468 "bare-module-lexer": "^1.0.0",
469 "bare-module-resolve": "^1.8.0",
470 "bare-path": "^3.0.0",
471 "bare-type-stripper": "^0.1.2",
472 "bare-url": "^2.0.1"
473 },
474 "engines": {
475 "bare": ">=1.29.4"
476 },
477 "peerDependencies": {
478 "bare-buffer": "*"
479 },
480 "peerDependenciesMeta": {
481 "bare-buffer": {
482 "optional": true
483 }
484 }
485 },
486 "node_modules/bare-module-lexer": {
487 "version": "1.6.0",
488 "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.6.0.tgz",
489 "integrity": "sha512-uVDdbh6EnypLg5K4Gz5GkFloXiJu0QN5S1JNiKdKbiLoyTjUqIXKnHR/8fb2QejDT/+5BNlnqlbkAAg1iEZSFA==",
490 "dev": true,
491 "license": "Apache-2.0",
492 "dependencies": {
493 "require-addon": "^1.0.2"
494 },
495 "peerDependencies": {
496 "bare-buffer": "*"
497 },
498 "peerDependenciesMeta": {
499 "bare-buffer": {
500 "optional": true
501 }
502 }
503 },
504 "node_modules/bare-module-resolve": {
505 "version": "1.12.2",
506 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.2.tgz",
507 "integrity": "sha512-j+hiD5k99qec4KjJvYsI67q5AOBifmy9JG3oeMVxTmvrhn2sIdp8StrUvZu4YNgwTpO+NhniQG16N1ETDe1k5w==",
508 "license": "Apache-2.0",
509 "dependencies": {
510 "bare-semver": "^1.0.0"
511 },
512 "peerDependencies": {
513 "bare-url": "*"
514 },
515 "peerDependenciesMeta": {
516 "bare-url": {
517 "optional": true
518 }
519 }
520 },
521 "node_modules/bare-net": {
522 "version": "2.3.2",
523 "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.2.tgz",
524 "integrity": "sha512-I+yz+pqbYsBkxDsnu5vkKvy7RSNY9CcAvu2jZT6PsmdXJQG1i3dmD5V7xc3334OVp2absgtUEYLmmuNFlphBzg==",
525 "dev": true,
526 "license": "Apache-2.0",
527 "dependencies": {
528 "bare-events": "^2.2.2",
529 "bare-pipe": "^4.0.0",
530 "bare-stream": "^2.0.0",
531 "bare-tcp": "^2.0.0"
532 }
533 },
534 "node_modules/bare-os": {
535 "version": "3.9.3",
536 "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz",
537 "integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==",
538 "devOptional": true,
539 "license": "Apache-2.0",
540 "engines": {
541 "bare": ">=1.14.0"
542 }
543 },
544 "node_modules/bare-path": {
545 "version": "3.0.1",
546 "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz",
547 "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==",
548 "devOptional": true,
549 "license": "Apache-2.0",
550 "dependencies": {
551 "bare-os": "^3.0.1"
552 }
553 },
554 "node_modules/bare-pipe": {
555 "version": "4.2.2",
556 "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.2.2.tgz",
557 "integrity": "sha512-Wkf6//CmKS8inHrkgi3X/xNMeaGfs2I0ayLM0B8cbPB4DxYHcoes6Ng+muCBfXNIJ9qmEjwDDJjAUz7nRxxjgQ==",
558 "dev": true,
559 "license": "Apache-2.0",
560 "peer": true,
561 "dependencies": {
562 "bare-events": "^2.0.0",
563 "bare-stream": "^2.0.0"
564 },
565 "engines": {
566 "bare": ">=1.16.0"
567 }
568 },
569 "node_modules/bare-posix": {
570 "version": "1.0.1",
571 "resolved": "https://registry.npmjs.org/bare-posix/-/bare-posix-1.0.1.tgz",
572 "integrity": "sha512-b4quA8dyRVvX0aFIAIiev5zFATHwIUGr42Gmt+CqiBNg/bdsNkrj4bbkeotMOMiljJS4DCME5kywJO/TFwKo1A==",
573 "dev": true,
574 "license": "Apache-2.0"
575 },
576 "node_modules/bare-process": {
577 "version": "4.5.0",
578 "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.0.tgz",
579 "integrity": "sha512-NTtYDiwleJjWWNg4+yzS6B/RovL/LLPHVvoOM+18W+dYAXCmky0zFfN66hqqBbrlGAlb52/QgKrdzPR3x1QyHA==",
580 "dev": true,
581 "license": "Apache-2.0",
582 "dependencies": {
583 "bare-abort": "^2.0.13",
584 "bare-env": "^3.0.0",
585 "bare-events": "^2.3.1",
586 "bare-hrtime": "^2.0.0",
587 "bare-os": "^3.7.1",
588 "bare-posix": "^1.0.1",
589 "bare-signals": "^4.0.0",
590 "bare-stdio": "^1.0.1"
591 }
592 },
593 "node_modules/bare-semver": {
594 "version": "1.1.0",
595 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.1.0.tgz",
596 "integrity": "sha512-1Hw5qJ7hXdVt3uPUqjeFTuxyvBUJauvz5A1I2jk8gzjZMHp04n//6nV9MDbG9CMw78JHY2lGV0w6s//LrASm2w==",
597 "license": "Apache-2.0"
598 },
599 "node_modules/bare-signals": {
600 "version": "4.2.0",
601 "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz",
602 "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==",
603 "dev": true,
604 "license": "Apache-2.0",
605 "dependencies": {
606 "bare-events": "^2.5.3",
607 "bare-os": "^3.3.1"
608 },
609 "engines": {
610 "bare": ">=1.7.0"
611 }
612 },
613 "node_modules/bare-stdio": {
614 "version": "1.0.2",
615 "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz",
616 "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==",
617 "dev": true,
618 "license": "Apache-2.0",
619 "dependencies": {
620 "bare-fs": "^4.5.2",
621 "bare-pipe": "^4.1.5",
622 "bare-tty": "^5.0.3"
623 }
624 },
625 "node_modules/bare-stream": {
626 "version": "2.13.3",
627 "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz",
628 "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==",
629 "license": "Apache-2.0",
630 "dependencies": {
631 "b4a": "^1.8.1",
632 "streamx": "^2.25.0",
633 "teex": "^1.0.1"
634 },
635 "peerDependencies": {
636 "bare-abort-controller": "*",
637 "bare-buffer": "*",
638 "bare-events": "*"
639 },
640 "peerDependenciesMeta": {
641 "bare-abort-controller": {
642 "optional": true
643 },
644 "bare-buffer": {
645 "optional": true
646 },
647 "bare-events": {
648 "optional": true
649 }
650 }
651 },
652 "node_modules/bare-structured-clone": {
653 "version": "1.6.0",
654 "resolved": "https://registry.npmjs.org/bare-structured-clone/-/bare-structured-clone-1.6.0.tgz",
655 "integrity": "sha512-AZjEERyqF7kAuudlmgrweT2KwwWM0LsGG4Gx/bWyFwJrKU7E3wNG8c09z2DIrdw93p3vDtfOX8fyHOcXfy5m+g==",
656 "dev": true,
657 "license": "Apache-2.0",
658 "dependencies": {
659 "bare-buffer": "^3.6.0",
660 "bare-type": "^1.1.0",
661 "bare-url": "^2.4.0",
662 "compact-encoding": "^3.0.1"
663 },
664 "engines": {
665 "bare": ">=1.2.0"
666 }
667 },
668 "node_modules/bare-stylize": {
669 "version": "0.0.1",
670 "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz",
671 "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==",
672 "dev": true,
673 "license": "Apache-2.0",
674 "dependencies": {
675 "bare-ansi-escapes": "^2.2.3",
676 "bare-process": "^4.2.1"
677 }
678 },
679 "node_modules/bare-subprocess": {
680 "version": "5.2.3",
681 "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
682 "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
683 "dev": true,
684 "license": "Apache-2.0",
685 "dependencies": {
686 "bare-env": "^3.0.0",
687 "bare-events": "^2.5.4",
688 "bare-os": "^3.0.1",
689 "bare-pipe": "^4.0.0",
690 "bare-url": "^2.2.2"
691 },
692 "engines": {
693 "bare": ">=1.7.0"
694 },
695 "peerDependencies": {
696 "bare-buffer": "*"
697 },
698 "peerDependenciesMeta": {
699 "bare-buffer": {
700 "optional": true
701 }
702 }
703 },
704 "node_modules/bare-tcp": {
705 "version": "2.5.1",
706 "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.5.1.tgz",
707 "integrity": "sha512-N32OosAegmGzksr/huxIYLU/uaUARte6utO+vAu/EPQztMM7iAC55VeQSfzr2/XBJoz2OIETMvmU7/sdfiOGjw==",
708 "dev": true,
709 "license": "Apache-2.0",
710 "dependencies": {
711 "bare-dns": "^2.0.4",
712 "bare-events": "^2.5.4",
713 "bare-stream": "^2.6.4"
714 },
715 "engines": {
716 "bare": ">=1.16.0"
717 },
718 "peerDependencies": {
719 "bare-pipe": "*"
720 },
721 "peerDependenciesMeta": {
722 "bare-pipe": {
723 "optional": true
724 }
725 }
726 },
727 "node_modules/bare-tls": {
728 "version": "3.1.7",
729 "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-3.1.7.tgz",
730 "integrity": "sha512-AMw8tJlb3LhzAmhgXRcjDrTlNxR3gXXyj6G8eU9iwvCFtiUBD8MxAW7bwunA1gXDukgo40A970jX0APc2jMU7A==",
731 "dev": true,
732 "license": "Apache-2.0",
733 "dependencies": {
734 "bare-net": "^2.0.1",
735 "bare-stream": "^2.6.4"
736 },
737 "engines": {
738 "bare": ">=1.7.0"
739 }
740 },
741 "node_modules/bare-tty": {
742 "version": "5.1.1",
743 "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.1.tgz",
744 "integrity": "sha512-cXCMAlcLCFTCWwCtXBIhDuXa4hutTEbrHHIK5pvSSb4BLaSGJ9tvxVN3mvGOvXXqONS7l04mTiP+eIUlZKI92g==",
745 "dev": true,
746 "license": "Apache-2.0",
747 "dependencies": {
748 "bare-events": "^2.2.0",
749 "bare-signals": "^4.0.0",
750 "bare-stream": "^2.0.0"
751 },
752 "engines": {
753 "bare": ">=1.16.0"
754 }
755 },
756 "node_modules/bare-type": {
757 "version": "1.1.0",
758 "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz",
759 "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==",
760 "license": "Apache-2.0",
761 "engines": {
762 "bare": ">=1.2.0"
763 }
764 },
765 "node_modules/bare-type-stripper": {
766 "version": "0.1.4",
767 "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.4.tgz",
768 "integrity": "sha512-FdZhp9XEnQpj8AWFmIft/sVUyKS9XSmB6PhcxBHhuEDxxZM5Kkt8+kFS7eEpLXR7TkaRkNpSENoGH/8lpAmtkA==",
769 "dev": true,
770 "license": "Apache-2.0",
771 "dependencies": {
772 "require-addon": "^1.0.2"
773 },
774 "peerDependencies": {
775 "bare-buffer": "*"
776 },
777 "peerDependenciesMeta": {
778 "bare-buffer": {
779 "optional": true
780 }
781 }
782 },
783 "node_modules/bare-url": {
784 "version": "2.4.5",
785 "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz",
786 "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==",
787 "devOptional": true,
788 "license": "Apache-2.0",
789 "dependencies": {
790 "bare-path": "^3.0.0"
791 }
792 },
793 "node_modules/bare-utils": {
794 "version": "1.6.0",
795 "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz",
796 "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==",
797 "dev": true,
798 "license": "Apache-2.0",
799 "dependencies": {
800 "bare-debug-log": "^2.0.0",
801 "bare-encoding": "^1.0.0",
802 "bare-format": "^1.0.0",
803 "bare-inspect": "^3.0.0",
804 "bare-stylize": "^0.0.1",
805 "bare-type": "^1.0.6"
806 }
807 },
808 "node_modules/bare-v8-to-istanbul": {
809 "version": "1.0.2",
810 "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz",
811 "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==",
812 "dev": true,
813 "license": "Apache-2.0",
814 "dependencies": {
815 "bare-assert": "^1.0.2",
816 "bare-fs": "^4.1.2",
817 "bare-module": "^6.1.2",
818 "bare-path": "^3.0.0",
819 "bare-process": "^4.2.0",
820 "bare-url": "^2.1.5",
821 "bare-utils": "^1.2.0",
822 "v8-to-istanbul": "^9.3.0",
823 "which-runtime": "^1.2.1"
824 }
825 },
826 "node_modules/bare-ws": {
827 "version": "3.0.0",
828 "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-3.0.0.tgz",
829 "integrity": "sha512-q2v0UIQ0cFQBXQMp+0FRaoSk1EoYgOhzO7yio0TqBV6Rkfot97mbBYxb1ssw5faVCisVaFxQYY8113SMLYQBow==",
830 "dev": true,
831 "license": "Apache-2.0",
832 "dependencies": {
833 "bare-crypto": "^1.2.0",
834 "bare-events": "^2.3.1",
835 "bare-http1": "^4.0.0",
836 "bare-https": "^3.0.0",
837 "bare-stream": "^2.1.2"
838 },
839 "peerDependencies": {
840 "bare-buffer": "*",
841 "bare-url": "*"
842 },
843 "peerDependenciesMeta": {
844 "bare-buffer": {
845 "optional": true
846 },
847 "bare-url": {
848 "optional": true
849 }
850 }
851 },
852 "node_modules/bip39-mnemonic": {
853 "version": "2.5.0",
854 "resolved": "https://registry.npmjs.org/bip39-mnemonic/-/bip39-mnemonic-2.5.0.tgz",
855 "integrity": "sha512-hgCxFHN129ta0bHl9VPykoqCPagBUr2oaXjfSbfeXAPe+y/byxwjPFFEFTMNO/1T/GSoMP1cOXHa2ZaprHG1kg==",
856 "license": "Apache-2.0",
857 "dependencies": {
858 "b4a": "^1.6.6",
859 "nanoassert": "^2.0.0",
860 "sodium-universal": "^5.0.1"
861 }
862 },
863 "node_modules/brittle": {
864 "version": "4.0.2",
865 "resolved": "https://registry.npmjs.org/brittle/-/brittle-4.0.2.tgz",
866 "integrity": "sha512-frnh7gB76uiSmFAf4+uIsaKjygMEvzUbN/GhxS+30WKz0pd/COBVuj+O9TmW9t0kIzJTafv8xquV6DriW316Vw==",
867 "dev": true,
868 "license": "Apache-2.0",
869 "dependencies": {
870 "b4a": "^1.8.0",
871 "bare-assert": "^1.2.0",
872 "bare-buffer": "^3.6.0",
873 "bare-channel": "^5.2.3",
874 "bare-cov": "^1.2.1",
875 "bare-fs": "^4.5.5",
876 "bare-os": "^3.7.1",
877 "bare-path": "^3.0.0",
878 "bare-process": "^4.3.0",
879 "bare-subprocess": "^5.2.2",
880 "bare-url": "^2.3.2",
881 "error-stack-parser": "^2.1.4",
882 "globbie": "^1.0.3",
883 "paparam": "^1.10.0",
884 "same-object": "^1.0.2",
885 "streamx": "^2.25.0",
886 "test-tmp": "^1.4.0",
887 "tmatch": "^5.0.0",
888 "which-runtime": "^1.3.2"
889 },
890 "bin": {
891 "brittle": "advise.js",
892 "brittle-bare": "brittle-bare.js",
893 "brittle-make-test": "make-test.js",
894 "brittle-node": "brittle-node.js"
895 }
896 },
897 "node_modules/cborg": {
898 "version": "5.1.6",
899 "resolved": "https://registry.npmjs.org/cborg/-/cborg-5.1.6.tgz",
900 "integrity": "sha512-apu/mOSODJ98JXBz3cbiv/gAFX4oaCZQQ53+8b4IPjc1wylYxd8eJis87QP7ic4AOE4O02j02jjfUgVZUJIIvA==",
901 "license": "Apache-2.0",
902 "bin": {
903 "cborg": "lib/bin.js"
904 }
905 },
906 "node_modules/compact-encoding": {
907 "version": "3.3.0",
908 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.3.0.tgz",
909 "integrity": "sha512-e64XyzlBvTRJ3iScuU/U2w25Dglkm7fhrRbrGaHIwuTlNnzjRKMaQBCVl7mJRvZg7wI5a9wX1IVTXCuaAANNkw==",
910 "license": "Apache-2.0",
911 "dependencies": {
912 "b4a": "^1.3.0"
913 }
914 },
915 "node_modules/convert-source-map": {
916 "version": "2.0.0",
917 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
918 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
919 "dev": true,
920 "license": "MIT"
921 },
922 "node_modules/error-stack-parser": {
923 "version": "2.1.4",
924 "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
925 "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
926 "dev": true,
927 "license": "MIT",
928 "dependencies": {
929 "stackframe": "^1.3.4"
930 }
931 },
932 "node_modules/events-universal": {
933 "version": "1.0.1",
934 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
935 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
936 "license": "Apache-2.0",
937 "dependencies": {
938 "bare-events": "^2.7.0"
939 }
940 },
941 "node_modules/fast-fifo": {
942 "version": "1.3.2",
943 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
944 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
945 "license": "MIT"
946 },
947 "node_modules/globbie": {
948 "version": "1.0.3",
949 "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz",
950 "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==",
951 "dev": true,
952 "license": "Apache-2.0",
953 "dependencies": {
954 "bare-fs": "^4.1.2",
955 "bare-path": "^3.0.0",
956 "bare-process": "^4.2.1",
957 "picomatch": "^4.0.2"
958 }
959 },
960 "node_modules/keet-identity-key": {
961 "version": "3.2.0",
962 "resolved": "https://registry.npmjs.org/keet-identity-key/-/keet-identity-key-3.2.0.tgz",
963 "integrity": "sha512-EMM+zOnDsdg/tOZ9gv+UlWjFYvMrTu+XYd2sNQ3+x5mGMxw5RjXvdXt/lh2OnGs04IemxKnHjk/hOMKjmYzhew==",
964 "license": "Apache-2.0",
965 "dependencies": {
966 "b4a": "^1.6.4",
967 "bip39-mnemonic": "^2.2.0",
968 "compact-encoding": "^3.0.0",
969 "nanoassert": "^2.0.0",
970 "sodium-hmac": "^2.0.1",
971 "sodium-universal": "^5.0.1"
972 }
973 },
974 "node_modules/multiformats": {
975 "version": "13.4.2",
976 "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-13.4.2.tgz",
977 "integrity": "sha512-eh6eHCrRi1+POZ3dA+Dq1C6jhP1GNtr9CRINMb67OKzqW9I5DUuZM/3jLPlzhgpGeiNUlEGEbkCYChXMCc/8DQ==",
978 "license": "Apache-2.0 OR MIT"
979 },
980 "node_modules/nanoassert": {
981 "version": "2.0.0",
982 "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
983 "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
984 "license": "ISC"
985 },
986 "node_modules/paparam": {
987 "version": "1.10.1",
988 "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz",
989 "integrity": "sha512-viyQI64VIja0Za3njIzhoEP8ZVkgPowhZPuG0E96NwBfYJ6ZIyrrlhWGtFPkdN7eYLl2L8CTWL+wla0evm1KKQ==",
990 "dev": true,
991 "license": "Apache-2.0"
992 },
993 "node_modules/picomatch": {
994 "version": "4.0.5",
995 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
996 "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
997 "dev": true,
998 "license": "MIT",
999 "engines": {
1000 "node": ">=12"
1001 },
1002 "funding": {
1003 "url": "https://github.com/sponsors/jonschlinkert"
1004 }
1005 },
1006 "node_modules/require-addon": {
1007 "version": "1.2.0",
1008 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz",
1009 "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==",
1010 "license": "Apache-2.0",
1011 "dependencies": {
1012 "bare-addon-resolve": "^1.3.0"
1013 },
1014 "engines": {
1015 "bare": ">=1.10.0"
1016 }
1017 },
1018 "node_modules/same-object": {
1019 "version": "1.0.2",
1020 "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz",
1021 "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==",
1022 "dev": true,
1023 "license": "MIT"
1024 },
1025 "node_modules/sodium-hmac": {
1026 "version": "2.1.0",
1027 "resolved": "https://registry.npmjs.org/sodium-hmac/-/sodium-hmac-2.1.0.tgz",
1028 "integrity": "sha512-1g7d7v+/0g/XXSX9sp0++X1QB6M1knhomnsFiN7B42b+rV0VhQeyiRzoWtp7vYGLkzmLY77tWNnCef3juGRwfQ==",
1029 "license": "Apache-2.0",
1030 "dependencies": {
1031 "b4a": "^1.6.6",
1032 "sodium-universal": "^5.0.1"
1033 }
1034 },
1035 "node_modules/sodium-native": {
1036 "version": "5.1.0",
1037 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz",
1038 "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==",
1039 "license": "MIT",
1040 "dependencies": {
1041 "bare-assert": "^1.2.0",
1042 "require-addon": "^1.1.0",
1043 "which-runtime": "^1.2.1"
1044 },
1045 "engines": {
1046 "bare": ">=1.16.0"
1047 }
1048 },
1049 "node_modules/sodium-universal": {
1050 "version": "5.0.1",
1051 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
1052 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
1053 "license": "MIT",
1054 "dependencies": {
1055 "sodium-native": "^5.0.1"
1056 },
1057 "peerDependencies": {
1058 "sodium-javascript": "~0.8.0"
1059 },
1060 "peerDependenciesMeta": {
1061 "sodium-javascript": {
1062 "optional": true
1063 }
1064 }
1065 },
1066 "node_modules/stackframe": {
1067 "version": "1.3.4",
1068 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
1069 "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
1070 "dev": true,
1071 "license": "MIT"
1072 },
1073 "node_modules/streamx": {
1074 "version": "2.28.0",
1075 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz",
1076 "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==",
1077 "license": "MIT",
1078 "dependencies": {
1079 "events-universal": "^1.0.0",
1080 "fast-fifo": "^1.3.2",
1081 "text-decoder": "^1.1.0"
1082 }
1083 },
1084 "node_modules/teex": {
1085 "version": "1.0.1",
1086 "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
1087 "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
1088 "license": "MIT",
1089 "dependencies": {
1090 "streamx": "^2.12.5"
1091 }
1092 },
1093 "node_modules/test-tmp": {
1094 "version": "1.4.0",
1095 "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz",
1096 "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==",
1097 "dev": true,
1098 "license": "MIT",
1099 "dependencies": {
1100 "bare-fs": "^4.0.1",
1101 "bare-os": "^3.3.0",
1102 "bare-path": "^3.0.0"
1103 }
1104 },
1105 "node_modules/text-decoder": {
1106 "version": "1.2.7",
1107 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
1108 "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
1109 "license": "Apache-2.0",
1110 "dependencies": {
1111 "b4a": "^1.6.4"
1112 }
1113 },
1114 "node_modules/tmatch": {
1115 "version": "5.0.0",
1116 "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
1117 "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==",
1118 "dev": true,
1119 "license": "ISC",
1120 "engines": {
1121 "node": ">=8"
1122 }
1123 },
1124 "node_modules/v8-to-istanbul": {
1125 "version": "9.3.0",
1126 "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
1127 "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
1128 "dev": true,
1129 "license": "ISC",
1130 "dependencies": {
1131 "@jridgewell/trace-mapping": "^0.3.12",
1132 "@types/istanbul-lib-coverage": "^2.0.1",
1133 "convert-source-map": "^2.0.0"
1134 },
1135 "engines": {
1136 "node": ">=10.12.0"
1137 }
1138 },
1139 "node_modules/which-runtime": {
1140 "version": "1.4.0",
1141 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz",
1142 "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==",
1143 "license": "Apache-2.0"
1144 }
1145 }
1146}
package.json +23 -0
……@@ -0,0 +1,23 @@
1{
2 "name": "atproto-identity",
3 "version": "0.0.1",
4 "description": "Seed-anchored atproto identity: one BIP39 mnemonic -> Ed25519 (Hypercore) + secp256k1 (did:plc). Offline did:plc genesis + verification.",
5 "type": "module",
6 "main": "lib/atproto-identity.js",
7 "scripts": {
8 "test": "brittle-node test/*.test.js",
9 "postinstall": "node scripts/bare-deps.mjs"
10 },
11 "dependencies": {
12 "@ipld/dag-cbor": "^9.2.1",
13 "@noble/curves": "^1.6.0",
14 "@noble/hashes": "^1.5.0",
15 "@scure/bip32": "^1.5.0",
16 "@scure/bip39": "^1.4.0",
17 "keet-identity-key": "^3.2.0",
18 "multiformats": "^13.3.0"
19 },
20 "devDependencies": {
21 "brittle": "^4.0.2"
22 }
23}
scripts/bare-deps.mjs +53 -0
……@@ -0,0 +1,53 @@
1// Bare dep compat: add a "bare" export condition to @noble/hashes' ./crypto so it resolves
2// to the bare-safe crypto.js (globalThis.crypto ?? undefined) instead of cryptoNode.js
3// (`import 'node:crypto'`, which Bare can't load). @noble/curves + @scure/* import
4// @noble/hashes, so this one condition unblocks the whole crypto tree. We sign
5// deterministically (RFC6979), so the shim degrading to undefined is never called.
6//
7// node_modules is reinstall-wiped, so this runs on `postinstall` (see the package.json of
8// each package that depends on @noble). Idempotent. Run manually with explicit roots
9// (`node scripts/bare-deps.mjs identity repo pds`) to patch the whole dev tree at once;
10// with no args it patches the CURRENT package (process.cwd()) — the postinstall case.
11
12import { readdirSync, readFileSync, writeFileSync, statSync } from 'node:fs'
13import { join } from 'node:path'
14
15function patch (pjPath) {
16 let j
17 try { j = JSON.parse(readFileSync(pjPath, 'utf8')) } catch { return false }
18 const exp = j.exports && j.exports['./crypto']
19 if (exp && typeof exp === 'object' && !exp.bare) {
20 j.exports['./crypto'] = { bare: './esm/crypto.js', ...exp } // "bare" first → matched before "node"
21 writeFileSync(pjPath, JSON.stringify(j, null, 2) + '\n')
22 return true
23 }
24 return false
25}
26
27const isDir = (p) => { try { return statSync(p).isDirectory() } catch { return false } }
28
29// Walk a node_modules dir: patch any @noble/hashes found, recurse into nested node_modules.
30function walk (nm, found) {
31 let entries
32 try { entries = readdirSync(nm, { withFileTypes: true }) } catch { return }
33 for (const e of entries) {
34 if (!e.isDirectory()) continue
35 const dir = join(nm, e.name)
36 if (e.name === '@noble') {
37 const pj = join(dir, 'hashes', 'package.json')
38 if (patch(pj)) found.push(pj)
39 // scoped pkgs may carry their own nested node_modules
40 for (const p of readdirSync(dir)) { const nn = join(dir, p, 'node_modules'); if (isDir(nn)) walk(nn, found) }
41 } else if (e.name.startsWith('@')) {
42 for (const p of readdirSync(dir)) { const nn = join(dir, p, 'node_modules'); if (isDir(nn)) walk(nn, found) }
43 } else {
44 const nn = join(dir, 'node_modules'); if (isDir(nn)) walk(nn, found)
45 }
46 }
47}
48
49const roots = process.argv.slice(2)
50if (roots.length === 0) roots.push(process.cwd())
51const found = []
52for (const r of roots) { const nm = join(r, 'node_modules'); if (isDir(nm)) walk(nm, found) }
53console.log(found.length ? `bare-deps: patched ${found.length} @noble/hashes` : 'bare-deps: nothing to patch')
test/attested-anchor.test.js +42 -0
……@@ -0,0 +1,42 @@
1// The composed device-attested anchor: one root + one device → both the atproto genesis
2// (federation authority) and the keet proof (P2P authority), consistent. Plus the
3// real-flow correctness point: the root builds the identical DID from only the device's
4// PUBLIC key.
5
6import test from 'brittle'
7import b4a from 'b4a'
8import { buildAttestedAnchor } from '../lib/attested-anchor.js'
9import { buildAttestedIdentity, verifyAttestedGenesis } from '../lib/device-attestation.js'
10import { deriveAtprotoKey } from '../lib/atproto-identity.js'
11import { verifyAtprotoBinding, rootIdentityPublicKey } from '../lib/device-chain.js'
12
13const ROOT = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
14const DEVICE = 'legal winner thank year wave sausage worth useful legal winner thank yellow'
15
16test('buildAttestedAnchor: atproto genesis + keet proof agree on one root and one device', async (t) => {
17 const a = await buildAttestedAnchor({ rootMnemonic: ROOT, deviceMnemonic: DEVICE, handle: 'p2p.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
18
19 // atproto side
20 const rootPk = deriveAtprotoKey(ROOT).publicKey
21 t.ok(a.did.startsWith('did:plc:'), 'did:plc minted')
22 t.is(a.signedOp.verificationMethods.atproto, a.deviceDidKey, 'verificationMethod = device signing key')
23 t.is(a.signedOp.rotationKeys[0], a.rootDidKey, 'rotationKeys[0] = root')
24 t.ok(verifyAttestedGenesis(a.signedOp, rootPk), 'genesis signed by root')
25 t.ok(a.deviceSigningKey && a.deviceSigningKey.privateKey.length === 32, 'device signing key available to the PDS')
26
27 // keet side — the proof resolves to the same root and vouches for this DID
28 const keetRoot = await rootIdentityPublicKey(ROOT)
29 const res = verifyAtprotoBinding({ proof: a.keetProof, did: a.did })
30 t.ok(res && b4a.equals(res.identityPublicKey, keetRoot), 'keet proof resolves to the ROOT identity')
31 t.ok(res && b4a.equals(res.devicePublicKey, a.deviceKeetPublicKey), 'keet proof leaf = this device')
32})
33
34test('root builds the SAME did:plc from only the device public key (device key never leaves the device)', (t) => {
35 const dev = deriveAtprotoKey(DEVICE)
36 const full = buildAttestedIdentity({ rootMnemonic: ROOT, deviceMnemonic: DEVICE, handle: 'p2p.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
37 const pubOnly = buildAttestedIdentity({ rootMnemonic: ROOT, deviceDidKey: dev.didKey, handle: 'p2p.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
38
39 t.is(pubOnly.did, full.did, 'pubkey-only genesis yields the identical permanent DID')
40 t.is(pubOnly.deviceSigningKey, null, 'root does NOT hold the device signing key in the pubkey-only flow')
41 t.is(pubOnly.deviceDidKey, dev.didKey, 'verificationMethod is the device pubkey the root was handed')
42})
test/binding.test.js +43 -0
……@@ -0,0 +1,43 @@
1// The binding: one mnemonic -> keet Ed25519 identity + atproto secp256k1/did:plc.
2// Async (keet-identity-key is async). Confirms both halves come from one phrase and
3// are mutually consistent + deterministic.
4
5import test from 'brittle'
6import { deriveIdentity, bindingRecord, deriveAtprotoKey } from '../lib/binding.js'
7
8const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
9// keet's Ed25519 identity pubkey for this phrase (captured from keet-identity-key).
10const KEET_ED25519 = 'de1ab94806ced05c64c0098de00ffadae052351b9fbdb6be4d645123ac9d8798'
11const OPTS = { handle: 'alice.7rnx.net', pdsUrl: 'https://pds.7rnx.net' }
12
13test('mnemonic binds keet Ed25519 identity to atproto secp256k1/did:plc', async (t) => {
14 const id = await deriveIdentity(MNEMONIC, OPTS)
15
16 // Ed25519 half comes from keet-identity-key, for this exact phrase.
17 t.ok(id.ed25519PublicKey.length === 32 && Buffer.from(id.ed25519PublicKey).toString('hex') === KEET_ED25519,
18 'ed25519 identity key is keet-derived (32 bytes, matches)')
19
20 // secp256k1 half is consistent with the standalone atproto derivation (same seed).
21 const standalone = deriveAtprotoKey(MNEMONIC)
22 t.ok(Buffer.from(id.secp256k1PublicKey).equals(Buffer.from(standalone.publicKey)),
23 'secp256k1 half matches standalone deriveAtprotoKey')
24 t.ok(id.didKey.startsWith('did:key:zQ3s'), 'atproto did:key is secp256k1 (zQ3s…)')
25 t.ok(/^did:plc:[a-z2-7]{24}$/.test(id.did), 'did:plc has the expected shape')
26
27 // One phrase -> both keys, deterministically.
28 const id2 = await deriveIdentity(MNEMONIC, OPTS)
29 t.is(id.did, id2.did, 'same mnemonic -> same did:plc')
30 t.ok(Buffer.from(id.ed25519PublicKey).equals(Buffer.from(id2.ed25519PublicKey)), 'same mnemonic -> same ed25519')
31 t.ok(!Buffer.from(id.ed25519PublicKey).equals(Buffer.from(id.secp256k1PublicKey)),
32 'the two keys differ (distinct curves/roles)')
33
34 // JSON-safe binding record for the hyper-identity provider.
35 const rec = await bindingRecord(MNEMONIC, OPTS)
36 t.is(JSON.stringify(rec), JSON.stringify(JSON.parse(JSON.stringify(rec))), 'binding record is JSON round-trippable')
37 t.is(rec.provider, 'atproto', 'record: provider=atproto')
38 t.is(rec.ed25519PublicKey.length, 64, 'record: ed25519 hex is 64 chars')
39 t.is(rec.secp256k1PublicKey.length, 66, 'record: secp256k1 hex is 66 chars (compressed)')
40 t.ok(rec.did === id.did && rec.handle === OPTS.handle, 'record: did + handle carried')
41 t.comment(`binding: ${rec.did} handle: ${rec.handle}`)
42 t.comment(`ed25519(keet): ${rec.ed25519PublicKey.slice(0, 16)}… secp256k1(atproto): ${rec.secp256k1PublicKey.slice(0, 16)}…`)
43})
test/derivation.test.js +56 -0
……@@ -0,0 +1,56 @@
1// Derivation + signing, all offline. Covers:
2// - did:key codec validated against the golden P-256 values (round-trip)
3// - deterministic secp256k1 derivation (same mnemonic -> same key/did)
4// - genesis sign -> verify round-trip, and tamper-detection
5// - full mnemonic -> did:plc flow is stable
6
7import test from 'brittle'
8import { decodeDidKey, encodeDidKey } from '../lib/did-key.js'
9import {
10 deriveAtprotoKey, buildGenesisOp, signOp, verifyOp,
11 createAtprotoIdentity, unsignedOp, didForCreateOp
12} from '../lib/atproto-identity.js'
13
14const b4a = (a, b) => Buffer.from(a).equals(Buffer.from(b))
15
16// A fixed, well-known BIP39 test mnemonic (NOT a real identity).
17const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
18
19test('did:key codec round-trips golden P-256 values', (t) => {
20 // did:key codec — golden P-256 values from millipds must round-trip byte-exact.
21 for (const golden of [
22 'did:key:zDnaeWUTD9gF8QMQ9WDh5t12ojkohiKGWUCrCnAGvu1CSJpyj',
23 'did:key:zDnaepXNQybJv5o8DM5AdmbpnedfApGv58q2UiDyhMgP5SFHx'
24 ]) {
25 const { curve, pubkey } = decodeDidKey(golden)
26 t.is(encodeDidKey(curve, pubkey), golden, `did:key P-256 round-trips (${golden.slice(0, 20)}…)`)
27 }
28})
29
30test('deterministic secp256k1 derivation', (t) => {
31 const k1 = deriveAtprotoKey(MNEMONIC)
32 const k2 = deriveAtprotoKey(MNEMONIC)
33 t.ok(b4a(k1.publicKey, k2.publicKey), 'same mnemonic -> same secp256k1 pubkey')
34 t.ok(k1.didKey.startsWith('did:key:zQ3s'), 'derives a secp256k1 did:key (zQ3s…)')
35 t.ok(b4a(decodeDidKey(k1.didKey).pubkey, k1.publicKey), 'did:key decodes back to the same pubkey')
36 t.ok(!b4a(deriveAtprotoKey(MNEMONIC, "m/44'/0'/0'/0/1").publicKey, k1.publicKey), 'different path -> different key')
37})
38
39test('genesis sign -> verify round-trip + tamper detection', (t) => {
40 const k1 = deriveAtprotoKey(MNEMONIC)
41 const unsigned = buildGenesisOp({ signingDidKey: k1.didKey, handle: 'alice.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
42 const sig = signOp(unsigned, k1.privateKey)
43 t.ok(verifyOp(unsigned, sig, k1.publicKey), 'own signature verifies')
44 const tampered = { ...unsigned, alsoKnownAs: ['at://mallory.7rnx.net'] }
45 t.ok(!verifyOp(tampered, sig, k1.publicKey), 'tampered op fails verification')
46})
47
48test('full flow is deterministic and self-consistent', (t) => {
49 const idA = createAtprotoIdentity(MNEMONIC, { handle: 'alice.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
50 const idB = createAtprotoIdentity(MNEMONIC, { handle: 'alice.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
51 t.is(idA.did, idB.did, 'same mnemonic+params -> same did:plc')
52 t.ok(/^did:plc:[a-z2-7]{24}$/.test(idA.did), 'did:plc has the expected shape')
53 t.is(didForCreateOp(idA.signedOp), idA.did, 'did recomputes from the signed op')
54 t.ok(verifyOp(unsignedOp(idA.signedOp), idA.signedOp.sig, idA.publicKey), 'signed op verifies end-to-end')
55 t.comment(`derived did: ${idA.did} handle: alice.7rnx.net`)
56})
test/device-attestation.test.js +48 -0
……@@ -0,0 +1,48 @@
1// Device-attested anchoring (atproto side): the genesis op must place the ROOT identity in
2// rotationKeys (authority/recovery) and the DEVICE key in verificationMethod (repo
3// signing), and be signed by the ROOT — never the device. This is the permanent shape
4// baked at genesis, so it must be correct before the one irreversible PLC write.
5
6import test from 'brittle'
7import { buildAttestedIdentity, verifyAttestedGenesis } from '../lib/device-attestation.js'
8import { deriveAtprotoKey } from '../lib/atproto-identity.js'
9
10// two distinct valid BIP39 vectors → distinct secp256k1 keys (root vs device)
11const ROOT = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
12const DEVICE = 'legal winner thank year wave sausage worth useful legal winner thank yellow'
13
14test('attested genesis: root=rotationKey, device=verificationMethod, root-signed', (t) => {
15 const id = buildAttestedIdentity({
16 rootMnemonic: ROOT, deviceMnemonic: DEVICE,
17 handle: 'p2p.7rnx.net', pdsUrl: 'https://pds.7rnx.net'
18 })
19 const op = id.signedOp
20
21 t.ok(id.did.startsWith('did:plc:'), 'did:plc minted from the attested genesis')
22 t.not(id.deviceDidKey, id.rootDidKey, 'device signing key ≠ root rotation key (separation)')
23 t.is(op.verificationMethods.atproto, id.deviceDidKey, 'verificationMethod = device signing key')
24 t.is(op.rotationKeys[0], id.rootDidKey, 'rotationKeys[0] = root (authority / recovery)')
25 t.ok(verifyAttestedGenesis(op, id.rootPublicKey), 'genesis is signed by the ROOT rotation key')
26 t.absent(verifyAttestedGenesis(op, id.deviceSigningKey.publicKey), 'genesis is NOT signed by the device key')
27 t.ok(id.deviceSigningKey.privateKey && id.deviceSigningKey.privateKey.length === 32,
28 'device holds a usable 32-byte signing key (it signs the repo commits)')
29})
30
31test('extra rotation keys form a recovery chain (root + device both authoritative)', (t) => {
32 const dev = deriveAtprotoKey(DEVICE)
33 const id = buildAttestedIdentity({
34 rootMnemonic: ROOT, deviceMnemonic: DEVICE, extraRotationDidKeys: [dev.didKey],
35 handle: 'p2p.7rnx.net', pdsUrl: 'https://pds.7rnx.net'
36 })
37 const rk = id.signedOp.rotationKeys
38 t.is(rk.length, 2, 'two rotation keys (a chain: root + device)')
39 t.is(rk[0], id.rootDidKey, 'root has priority (first in the ordered rotation set)')
40 t.is(rk[1], dev.didKey, 'device is also a rotation key — it can recover if the root is lost')
41})
42
43test('the did:plc changes if the rotation set changes (rotation is baked at genesis)', (t) => {
44 const a = buildAttestedIdentity({ rootMnemonic: ROOT, deviceMnemonic: DEVICE, handle: 'p2p.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
45 const dev = deriveAtprotoKey(DEVICE)
46 const b = buildAttestedIdentity({ rootMnemonic: ROOT, deviceMnemonic: DEVICE, extraRotationDidKeys: [dev.didKey], handle: 'p2p.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
47 t.not(a.did, b.did, 'a different rotation set yields a different permanent DID — decide it before --publish')
48})
test/device-chain.test.js +39 -0
……@@ -0,0 +1,39 @@
1// keet device chain (Ed25519, P2P side): root identity attests a device, the device
2// vouches for the atproto DID, and a verifier resolves the chain back to the root — the
3// attestation OUR P2P world checks, parallel to atproto's PLC authority.
4
5import test from 'brittle'
6import b4a from 'b4a'
7import {
8 deviceKeetKeyPair, rootIdentityPublicKey,
9 attestDeviceChain, attestAtprotoBinding, verifyAtprotoBinding
10} from '../lib/device-chain.js'
11
12const ROOT = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
13const DEVICE = 'legal winner thank year wave sausage worth useful legal winner thank yellow'
14const DID = 'did:plc:waj4hyx6pr5zdwaccrcbsfti'
15
16test('device chain binds the atproto DID to the root identity', async (t) => {
17 const deviceKp = await deviceKeetKeyPair(DEVICE)
18 const chain = await attestDeviceChain({ rootMnemonic: ROOT, devicePublicKey: deviceKp.publicKey })
19 const bound = attestAtprotoBinding({ deviceKeyPair: deviceKp, did: DID, proof: chain })
20
21 const res = verifyAtprotoBinding({ proof: bound, did: DID })
22 const rootPk = await rootIdentityPublicKey(ROOT)
23
24 t.ok(res, 'binding proof verifies')
25 t.ok(res && b4a.equals(res.identityPublicKey, rootPk), 'chain resolves to the ROOT identity')
26 t.ok(res && b4a.equals(res.devicePublicKey, deviceKp.publicKey), 'the device is the attested leaf')
27})
28
29test('binding is rejected for a different DID or a wrong expected device', async (t) => {
30 const deviceKp = await deviceKeetKeyPair(DEVICE)
31 const chain = await attestDeviceChain({ rootMnemonic: ROOT, devicePublicKey: deviceKp.publicKey })
32 const bound = attestAtprotoBinding({ deviceKeyPair: deviceKp, did: DID, proof: chain })
33
34 t.absent(verifyAtprotoBinding({ proof: bound, did: 'did:plc:someoneelsesdifferentdid' }),
35 'a different DID → rejected (device signed the real DID)')
36 const otherKp = await deviceKeetKeyPair(ROOT)
37 t.absent(verifyAtprotoBinding({ proof: bound, did: DID, expectedDevice: otherKp.publicKey }),
38 'a wrong expected device → rejected')
39})
test/golden-did-plc.test.js +38 -0
……@@ -0,0 +1,38 @@
1// Golden-vector test: prove our did:plc computation is byte-exact against a DID
2// that millipds actually minted and that lives on the real PLC directory.
3//
4// did:plc:bh2hiuoeb34g4lif62zj2vx7 (handle bridgetest.7rnx.net)
5//
6// If dag-cbor canonicalization, sha256, base32, or truncation are off by a byte,
7// this fails. Reference IS the spec (verification-first).
8
9import test from 'brittle'
10import { readFileSync } from 'node:fs'
11import { fileURLToPath } from 'node:url'
12import { dirname, join } from 'node:path'
13import { didForCreateOp } from '../lib/did-plc.js'
14
15const here = dirname(fileURLToPath(import.meta.url))
16const golden = JSON.parse(readFileSync(join(here, 'vectors', 'bridgetest-genesis.json'), 'utf8'))
17
18const EXPECTED = 'did:plc:bh2hiuoeb34g4lif62zj2vx7'
19
20test('did:plc is byte-exact against millipds golden genesis op', (t) => {
21 const did = didForCreateOp(golden)
22 t.is(did, EXPECTED, 'did:plc from millipds golden genesis op')
23
24 // The DID is a pure function of the op — recomputation is stable.
25 t.is(didForCreateOp(golden), did, 'recomputation is deterministic')
26
27 // Reordering the JSON keys must NOT change the DID (DAG-CBOR canonicalizes).
28 const reordered = {
29 sig: golden.sig,
30 prev: golden.prev,
31 services: golden.services,
32 alsoKnownAs: golden.alsoKnownAs,
33 verificationMethods: golden.verificationMethods,
34 rotationKeys: golden.rotationKeys,
35 type: golden.type
36 }
37 t.is(didForCreateOp(reordered), EXPECTED, 'DID invariant under key reordering')
38})
test/vectors/bridgetest-genesis.json +16 -0
……@@ -0,0 +1,16 @@
1{
2 "type": "plc_operation",
3 "rotationKeys": ["did:key:zDnaeWUTD9gF8QMQ9WDh5t12ojkohiKGWUCrCnAGvu1CSJpyj"],
4 "verificationMethods": {
5 "atproto": "did:key:zDnaepXNQybJv5o8DM5AdmbpnedfApGv58q2UiDyhMgP5SFHx"
6 },
7 "alsoKnownAs": ["at://bridgetest.7rnx.net"],
8 "services": {
9 "atproto_pds": {
10 "type": "AtprotoPersonalDataServer",
11 "endpoint": "https://pds.7rnx.net"
12 }
13 },
14 "prev": null,
15 "sig": "fN81wwBBSdc7VjydAmv_1aM5qn747LyVtzxj0IVWBOlRgxJ7gSTwgsJVgRBzHiUsu_zIRIiUMMYheMp3RFhYcw"
16}