@serfab/cadre-core 0.8.1 → 0.9.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 +2 -2
- package/dist/cadre-node.d.ts +101 -61
- package/dist/cadre-node.js +165 -124
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +15 -15
- package/dist/control-cohort.js +14 -14
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +51 -34
- package/dist/control-database.js +72 -56
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +2 -2
- package/dist/control-formation-recorder.js +2 -2
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +57 -33
- package/dist/control-schema.js.map +1 -1
- package/dist/device-token.d.ts +1 -1
- package/dist/device-token.js +1 -1
- package/dist/{authority-key.d.ts → ed25519-key.d.ts} +11 -10
- package/dist/{authority-key.js → ed25519-key.js} +9 -10
- package/dist/ed25519-key.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/key-store.d.ts +1 -1
- package/dist/key-store.js +1 -1
- package/dist/peer-authorization.d.ts +28 -6
- package/dist/peer-authorization.js +33 -7
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-record.d.ts +3 -3
- package/dist/peer-record.js +3 -3
- package/dist/push-node.d.ts +38 -0
- package/dist/push-node.js +48 -0
- package/dist/push-node.js.map +1 -0
- package/dist/push-notifier-apns.d.ts +10 -2
- package/dist/push-notifier-apns.js +14 -10
- package/dist/push-notifier-apns.js.map +1 -1
- package/dist/push-notifier-fcm.d.ts +7 -1
- package/dist/push-notifier-fcm.js +9 -10
- package/dist/push-notifier-fcm.js.map +1 -1
- package/dist/push-notifier.d.ts +16 -35
- package/dist/push-notifier.js +16 -44
- package/dist/push-notifier.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +70 -51
- package/dist/seed-bootstrap.js +168 -106
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +11 -11
- package/dist/seed-trust-policy.js +11 -11
- package/dist/seed-trust-policy.js.map +1 -1
- package/dist/strand-database.d.ts +5 -5
- package/dist/strand-database.js +4 -4
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +1 -1
- package/dist/strand-formation-protocol.js +1 -1
- package/dist/strand-instance-manager.d.ts +1 -1
- package/dist/strand-instance-manager.js +1 -1
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +6 -6
- package/dist/strand-member-key.js +6 -6
- package/dist/strand-member-key.js.map +1 -1
- package/dist/strand-member-registry.d.ts +7 -7
- package/dist/strand-member-registry.js +5 -5
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +84 -84
- package/dist/strand-membership-writer.js +90 -90
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/types.d.ts +52 -32
- package/dist/types.js.map +1 -1
- package/package.json +7 -3
- package/src/cadre-node.ts +170 -126
- package/src/control-cohort.ts +17 -17
- package/src/control-database.ts +76 -59
- package/src/control-formation-recorder.ts +2 -2
- package/src/control-schema.ts +57 -33
- package/src/device-token.ts +1 -1
- package/src/{authority-key.ts → ed25519-key.ts} +12 -11
- package/src/index.ts +21 -19
- package/src/key-store.ts +1 -1
- package/src/peer-authorization.ts +35 -7
- package/src/peer-record.ts +3 -3
- package/src/push-node.ts +64 -0
- package/src/push-notifier-apns.ts +16 -12
- package/src/push-notifier-fcm.ts +9 -10
- package/src/push-notifier.ts +16 -57
- package/src/seed-bootstrap.ts +179 -115
- package/src/seed-trust-policy.ts +18 -18
- package/src/strand-database.ts +6 -6
- package/src/strand-formation-protocol.ts +1 -1
- package/src/strand-instance-manager.ts +2 -2
- package/src/strand-member-key.ts +7 -7
- package/src/strand-member-registry.ts +10 -10
- package/src/strand-membership-writer.ts +127 -127
- package/src/types.ts +52 -32
- package/dist/authority-key.js.map +0 -1
|
@@ -10,7 +10,7 @@ const NEVER_EXPIRES = new Date(8640000000000000);
|
|
|
10
10
|
* usage questions, and writes the consent row that records a redemption. Two
|
|
11
11
|
* provisioning shapes are supported, keyed on whether the invite binds a host strand:
|
|
12
12
|
*
|
|
13
|
-
* - **Bound (provision-then-record):** the host strand already exists (
|
|
13
|
+
* - **Bound (provision-then-record):** the host strand already exists (owner-signed
|
|
14
14
|
* up front and named by the invite's `StrandId`), so {@link resolveStrand} reports it
|
|
15
15
|
* and {@link recordUsage} writes the consent row against that pre-existing strand
|
|
16
16
|
* (record-only) rather than inserting a new `Strand`.
|
|
@@ -67,7 +67,7 @@ export class ControlFormationUsageRecorder {
|
|
|
67
67
|
* Record consent against an **already-existing** host strand (record-only): the
|
|
68
68
|
* single `FormationUsage` insert auto-commits and the deferred `StrandExists`
|
|
69
69
|
* CHECK is satisfied by the pre-existing strand. This is the provision-then-record
|
|
70
|
-
* commitment — the strand was minted
|
|
70
|
+
* commitment — the strand was minted owner-signed up front, so we do NOT
|
|
71
71
|
* re-insert it (which would double-insert the same PK). `initiatorKey` is carried
|
|
72
72
|
* as the usage `PeerId` (advisory). Use {@link ControlDatabase.redeemInvitation}
|
|
73
73
|
* for the consent-creates-strand path instead.
|
package/dist/control-schema.d.ts
CHANGED
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
* build if they drift. Any edit here must be mirrored in `schemas/control.qsql`
|
|
10
10
|
* and vice versa.
|
|
11
11
|
*/
|
|
12
|
-
export declare const CONTROL_SCHEMA = "-- This manages a Sereus party's cadre, or set of nodes, and their participation in strands (networks)\ndeclare schema CadreControl {\n -- A key that can authorize various control changes\n table AuthorityKey (\n Key text primary key,\n StampId text not null unique, -- single-use authorization nonce (anti-replay)\n constraint Authorized check (\n -- Bootstrap: first authority key needs no existing authorization\n (select count(1) from AuthorityKey) <= 1\n\n -- Old authority authorizes by signing over THIS row (Key, StampId); single-use via unique StampId\n or (old.Key is not null and old.Key = context.AuthorityKey and verify(digest(new.Key, new.StampId), context.Signature, old.Key, 'ed25519'))\n\n -- or other authorities authorize by signing over THIS row (Key, StampId); single-use via unique StampId\n or exists (select 1 from AuthorityKey A where A.Key = context.AuthorityKey and verify(digest(new.Key, new.StampId), context.Signature, A.Key, 'ed25519'))\n )\n ) with context (AuthorityKey text null, Signature text null);\n\n -- A key that can validate a strand formation disclosure\n table ValidationKey (\n Key text primary key,\n StampId text not null unique, -- single-use authorization nonce (anti-replay)\n constraint Authorized check (\n -- Authorities authorize by signing over THIS row (Key, StampId); single-use via unique StampId\n exists (select 1 from AuthorityKey A where A.Key = context.AuthorityKey and verify(digest(new.Key, new.StampId), context.Signature, A.Key, 'ed25519'))\n )\n ) with context (AuthorityKey text, Signature text);\n\n -- A network of members sharing an sApp database, each contributing peer nodes (their cadre) to the overall cohort\n -- Cadre peers should participate in each of these strands\n table Strand (\n Id text primary key, -- UUID\n MemberPrivateKey text null unique, -- Our private key as a member of this strand\n Type text, -- Types: 'o' = Open, 'c' = Closed -- Open can still control writes in the sApp, but only Closed controls reads\n StampId text not null unique, -- single-use authorization nonce (anti-replay)\n constraint Authorized check (\n -- Authorized by an authority signing over THIS row (Id, Type, MemberPrivateKey, StampId); single-use via unique StampId\n exists (select 1 from AuthorityKey A where A.Key = context.AuthorityKey and verify(digest(new.Id, new.Type, coalesce(new.MemberPrivateKey, ''), new.StampId), context.Signature, A.Key, 'ed25519'))\n\n -- or authorized by a cadre peer who has received a valid formation invitation.\n -- That path has no signed writer yet; when one is built it must still supply a\n -- fresh, unique StampId to satisfy the not-null/unique anti-replay column.\n or exists (select 1 from FormationUsage FU where FU.StrandId = new.Id)\n ),\n constraint MemberKeyClosedOnly check (\n -- An open strand ('o') has no membership gate, so it must not carry a\n -- member key. A non-null MemberPrivateKey requires a closed strand ('c').\n new.MemberPrivateKey is null or new.Type = 'c'\n )\n ) with context (AuthorityKey text, Signature text);\n\n -- A peer (node) that is part of the cadre, carrying a self-published,\n -- freshness-stamped, self-signed address record (see PeerAddressRecord in cadre-core).\n -- The row IS the peer-address record: a resolver reads it, re-verifies Sig against\n -- PublicKey, checks freshness (UpdatedAt) and trust, then dials the addrs.\n table CadrePeer (\n PeerId text primary key,\n PublicKey text null, -- ed25519 (base64url) whose libp2p identity == PeerId (null if non-Ed25519)\n Multiaddr text, -- comma-joined current addrs (signaling / p2p-circuit first)\n UpdatedAt int null, -- epoch ms; strictly increases per self-update (replay/rollback guard)\n Sig text null, -- ed25519 self-signature over the signed payload (base64url); null until self-published\n constraint AuthorizedInsert check on insert, delete (\n -- Authorized by an authority key, which vouches both membership and the\n -- PublicKey<->PeerId binding (cadre-core derives PublicKey from PeerId before insert).\n -- The single-field digest frames the base58btc PeerId as TEXT, matching\n -- cadre-core peer-authorization.ts:peerAuthorizationDigest.\n exists (select 1 from AuthorityKey A where A.Key = context.AuthorityKey and verify(digest(coalesce(new.PeerId, old.PeerId)), context.Signature, A.Key, 'ed25519'))\n ),\n constraint AuthorizedUpdate check on update (\n -- Peer self-updates its own addrs + freshness, signing with its OWN ed25519 key\n -- (the key behind PeerId). PeerId/PublicKey are immutable on self-update, UpdatedAt\n -- must strictly increase (replay guard), and Sig is verified against the stored\n -- PublicKey over the same payload the publish path signs\n -- (cadre-core peer-record.ts:peerRecordSignedPayload).\n (\n new.PeerId = old.PeerId\n and new.PublicKey = old.PublicKey\n and new.UpdatedAt > coalesce(old.UpdatedAt, 0)\n and verify(\n digest(new.PeerId || '|' || new.Multiaddr || '|' || cast(new.UpdatedAt as text)),\n new.Sig, new.PublicKey, 'ed25519')\n )\n -- or an authority re-authorizes (rotation / correction)\n or exists (select 1 from AuthorityKey A where A.Key = context.AuthorityKey and verify(digest(new.PeerId), context.Signature, A.Key, 'ed25519'))\n )\n ) with context (AuthorityKey text null, Signature text);\n\n -- A mobile cadre peer's platform push token (FCM/APNs), self-published so a\n -- server peer can deliver a push-wake to a suspended app over the platform push\n -- channel (a control-network dial cannot reach an OS-suspended process). The row\n -- mirrors CadrePeer: a resolver re-verifies Sig against the CadrePeer.PublicKey for\n -- this PeerId (see device-token.ts:deviceTokenSignedPayload), checks freshness, then\n -- hands the token to the push sender.\n table DeviceToken (\n PeerId text primary key, -- the CadrePeer this token belongs to\n Platform text not null, -- 'fcm' | 'apns'\n Token text not null, -- opaque platform device/registration token\n UpdatedAt int null, -- epoch ms; strictly increases per self-update (replay guard)\n Sig text null, -- ed25519 self-sig over (PeerId|Platform|Token|UpdatedAt)\n constraint AuthorizedInsert check on insert, delete (\n -- Authorized by an authority key (membership vouch), exactly like CadrePeer.\n -- The single-field digest frames the base58btc PeerId as TEXT.\n exists (select 1 from AuthorityKey A where A.Key = context.AuthorityKey and verify(digest(coalesce(new.PeerId, old.PeerId)), context.Signature, A.Key, 'ed25519'))\n ),\n constraint AuthorizedUpdate check on update (\n -- Peer self-updates its own token, signing with its OWN ed25519 key (the key\n -- behind PeerId). PeerId is immutable, UpdatedAt must strictly increase (replay\n -- guard), and Sig is verified against the stored CadrePeer.PublicKey over the\n -- signed payload (cadre-core device-token.ts:deviceTokenSignedPayload). Platform\n -- and Token may change on self-update (platform switch / reinstall / rotation).\n (\n new.PeerId = old.PeerId\n and new.UpdatedAt > coalesce(old.UpdatedAt, 0)\n and exists (select 1 from CadrePeer P where P.PeerId = new.PeerId and verify(\n digest(new.PeerId || '|' || new.Platform || '|' || new.Token || '|' || cast(new.UpdatedAt as text)),\n new.Sig, P.PublicKey, 'ed25519'))\n )\n -- or an authority re-authorizes (rotation / correction)\n or exists (select 1 from AuthorityKey A where A.Key = context.AuthorityKey and verify(digest(new.PeerId), context.Signature, A.Key, 'ed25519'))\n )\n ) with context (AuthorityKey text null, Signature text);\n\n -- An open invitation to form a strand with this party\n table FormationInvite (\n Token text primary key, -- Just a random string\n sAppId text, -- The app for the strand that will be formed\n ExpiresAt datetime null,\n TotalUses int null check (TotalUses >= 0),\n ValidationUrl text null, -- Web hook - send disclosure, IP address...\n StrandId text null, -- Host strand this invite binds to (provision-then-record). Non-null => the responder records consent against this pre-existing strand; null => legacy responder-provisions path.\n StampId text not null unique, -- single-use authorization nonce (anti-replay)\n constraint AuthorizedAddOrRemove check on insert, delete (\n -- Authorized by an authority signing over THIS row\n -- (Token, sAppId, ExpiresAt, TotalUses, ValidationUrl, StrandId, StampId); single-use via unique StampId.\n -- coalesce(new.F, old.F) binds the NEW row on insert and the OLD row on delete (cf. CadrePeer).\n -- StrandId is a nullable bound field: it signs as '' when absent (cf. ValidationUrl).\n exists (select 1 from AuthorityKey A where A.Key = context.AuthorityKey and verify(\n digest(\n coalesce(new.Token, old.Token),\n coalesce(new.sAppId, old.sAppId),\n coalesce(cast(coalesce(new.ExpiresAt, old.ExpiresAt) as text), ''),\n coalesce(cast(coalesce(new.TotalUses, old.TotalUses) as text), ''),\n coalesce(coalesce(new.ValidationUrl, old.ValidationUrl), ''),\n coalesce(coalesce(new.StrandId, old.StrandId), ''),\n coalesce(new.StampId, old.StampId)\n ),\n context.Signature, A.Key, 'ed25519'))\n ),\n -- Invites are insert/delete only: forbid in-place mutation so the row-bound\n -- AuthorizedAddOrRemove signature cannot be sidestepped by updating consent\n -- parameters (TotalUses/ExpiresAt/...) after a legitimate insert. A bare check\n -- defaults to insert+update; the check on insert, delete above excludes update,\n -- so this guard is required. Mirrors FormationUsage.InsertOnly.\n constraint Immutable check on update (false)\n ) with context (AuthorityKey text, Signature text);\n\n table FormationUsage (\n Token text,\n UseNumber int,\n Disclosure text,\n StrandId text,\n primary key (Token, UseNumber),\n constraint InsertOnly check on update, delete (false),\n constraint Monotonic check (\n -- Read the PRE-transaction snapshot (committed.*): this CHECK auto-defers\n -- to commit (it has a subquery), by which point the row being inserted is\n -- live, so a plain from-FormationUsage would count this row itself and make\n -- UseNumber = max+1 unsatisfiable. committed.* excludes the in-flight row,\n -- giving sequential 1,2,3... per token (cross-transaction; concurrent\n -- redemptions of the same token collide on the (Token, UseNumber) PK).\n new.UseNumber = coalesce((select max(UseNumber) from committed.FormationUsage U where U.Token = new.Token), 0) + 1\n ),\n constraint Authorized check on insert (\n -- Satisfies an invitation\n exists (\n select 1 from FormationInvite FI\n where FI.Token = new.Token\n and (FI.TotalUses is null or FI.TotalUses >= new.UseNumber)\n and (FI.ExpiresAt is null or FI.ExpiresAt > context.Now)\n and (FI.ValidationUrl is null or verify(digest(new.Token || new.Disclosure), context.ValidationSignature, context.ValidationKey, 'ed25519'))\n )\n ),\n constraint StrandExists check (exists (select 1 from Strand S where S.Id = new.StrandId)),\n ) with context (PeerId text, PeerSignature text, Now datetime, ValidationKey text null, ValidationSignature text null);\n}\n\napply schema CadreControl;";
|
|
12
|
+
export declare const CONTROL_SCHEMA = "-- This manages a Sereus party's cadre, or set of nodes, and their participation in strands (networks)\ndeclare schema CadreControl {\n -- A key that can authorize various control changes\n table OwnerKey (\n Key text primary key,\n StampId text not null unique, -- single-use authorization nonce (anti-replay)\n constraint Authorized check (\n -- Bootstrap: first owner key needs no existing authorization\n (select count(1) from OwnerKey) <= 1\n\n -- Old owner authorizes by signing over THIS row (Key, StampId); single-use via unique StampId\n or (old.Key is not null and old.Key = context.OwnerKey and verify(digest(new.Key, new.StampId), context.Signature, old.Key, 'ed25519'))\n\n -- or other owners authorize by signing over THIS row (Key, StampId); single-use via unique StampId\n or exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.Key, new.StampId), context.Signature, A.Key, 'ed25519'))\n )\n ) with context (OwnerKey text null, Signature text null);\n\n -- A key that can validate a strand formation disclosure\n table ValidationKey (\n Key text primary key,\n StampId text not null unique, -- single-use authorization nonce (anti-replay)\n constraint Authorized check (\n -- Owners authorize by signing over THIS row (Key, StampId); single-use via unique StampId\n exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.Key, new.StampId), context.Signature, A.Key, 'ed25519'))\n )\n ) with context (OwnerKey text, Signature text);\n\n -- A network of members sharing an sApp database, each contributing peer nodes (their cadre) to the overall cohort\n -- Cadre peers should participate in each of these strands\n table Strand (\n Id text primary key, -- UUID\n MemberPrivateKey text null unique, -- Our private key as a member of this strand\n Type text, -- Types: 'o' = Open, 'c' = Closed -- Open can still control writes in the sApp, but only Closed controls reads\n StampId text not null unique, -- single-use authorization nonce (anti-replay)\n constraint Authorized check (\n -- Authorized by an owner signing over THIS row (Id, Type, MemberPrivateKey, StampId); single-use via unique StampId\n exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.Id, new.Type, coalesce(new.MemberPrivateKey, ''), new.StampId), context.Signature, A.Key, 'ed25519'))\n\n -- or authorized by a cadre peer who has received a valid formation invitation.\n -- That path has no signed writer yet; when one is built it must still supply a\n -- fresh, unique StampId to satisfy the not-null/unique anti-replay column.\n or exists (select 1 from FormationUsage FU where FU.StrandId = new.Id)\n ),\n constraint MemberKeyClosedOnly check (\n -- An open strand ('o') has no membership gate, so it must not carry a\n -- member key. A non-null MemberPrivateKey requires a closed strand ('c').\n new.MemberPrivateKey is null or new.Type = 'c'\n )\n ) with context (OwnerKey text, Signature text);\n\n -- A peer (node) that is part of the cadre, carrying a self-published,\n -- freshness-stamped, self-signed address record (see PeerAddressRecord in cadre-core).\n -- The row IS the peer-address record: a resolver reads it, re-verifies Sig against\n -- PublicKey, checks freshness (UpdatedAt) and trust, then dials the addrs.\n table CadrePeer (\n PeerId text primary key,\n PublicKey text null, -- ed25519 (base64url) whose libp2p identity == PeerId (null if non-Ed25519)\n Multiaddr text, -- comma-joined current addrs (signaling / p2p-circuit first)\n UpdatedAt int null, -- epoch ms; strictly increases per self-update (replay/rollback guard)\n Sig text null, -- ed25519 self-signature over the signed payload (base64url); null until self-published\n StampId text not null unique, -- single-use authorization nonce (anti-replay), bound into the voucher/remove digests; rotates on (re)insert\n VouchOwner text null, -- ed25519 (base64url) owner key that vouched this membership (== insert context.OwnerKey)\n VouchSig text null, -- that owner's signature over digest(PeerId, StampId) (== insert context.Signature). A reader checks VouchOwner against its NODE-LOCAL trusted-owner anchor (not this replicated table, which a self-owner can pollute) to decide authorized membership.\n constraint AuthorizedInsert check on insert (\n -- Authorized by an owner key, which vouches both membership and the\n -- PublicKey<->PeerId binding (cadre-core derives PublicKey from PeerId before insert).\n -- The digest binds PeerId + the single-use StampId nonce (two TEXT fields),\n -- matching cadre-core peer-authorization.ts:cadrePeerVoucherDigest.\n exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.PeerId, new.StampId), context.Signature, A.Key, 'ed25519'))\n -- Persist the vouching (owner, signature) onto the row so a reader can later\n -- re-check VouchOwner against its node-local anchor. The stored pair MUST equal\n -- the pair the verify above validated, so a writer cannot store a voucher it did\n -- not actually receive a signature for.\n and new.VouchOwner = context.OwnerKey\n and new.VouchSig = context.Signature\n ),\n constraint AuthorizedDelete check on delete (\n -- Delete is authorized by a signature over a DISTINCT 'remove'-scoped digest bound\n -- to the row's StampId (cadre-core peer-authorization.ts:cadrePeerRemoveDigest), so\n -- the stored voucher (a signature over digest(PeerId, StampId)) can NEVER be replayed\n -- to authorize a delete. The remove signature rides in context and is never stored.\n exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(old.PeerId, old.StampId, 'remove'), context.Signature, A.Key, 'ed25519'))\n ),\n constraint AuthorizedUpdate check on update (\n -- Peer self-updates its own addrs + freshness, signing with its OWN ed25519 key\n -- (the key behind PeerId). PeerId/PublicKey/StampId/voucher are immutable on\n -- self-update, UpdatedAt must strictly increase (replay guard), and Sig is verified\n -- against the stored PublicKey over the same payload the publish path signs\n -- (cadre-core peer-record.ts:peerRecordSignedPayload).\n (\n new.PeerId = old.PeerId\n and new.PublicKey = old.PublicKey\n and new.StampId = old.StampId\n and new.VouchOwner = old.VouchOwner\n and new.VouchSig = old.VouchSig\n and new.UpdatedAt > coalesce(old.UpdatedAt, 0)\n and verify(\n digest(new.PeerId || '|' || new.Multiaddr || '|' || cast(new.UpdatedAt as text)),\n new.Sig, new.PublicKey, 'ed25519')\n )\n -- or an owner re-authorizes (rotation / correction): re-vouches the row over\n -- its current StampId, so the stored voucher is re-bound to the re-authorizing owner.\n or (\n exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.PeerId, new.StampId), context.Signature, A.Key, 'ed25519'))\n and new.VouchOwner = context.OwnerKey\n and new.VouchSig = context.Signature\n )\n )\n ) with context (OwnerKey text null, Signature text);\n\n -- A mobile cadre peer's platform push token (FCM/APNs), self-published so a\n -- server peer can deliver a push-wake to a suspended app over the platform push\n -- channel (a control-network dial cannot reach an OS-suspended process). The row\n -- mirrors CadrePeer: a resolver re-verifies Sig against the CadrePeer.PublicKey for\n -- this PeerId (see device-token.ts:deviceTokenSignedPayload), checks freshness, then\n -- hands the token to the push sender.\n table DeviceToken (\n PeerId text primary key, -- the CadrePeer this token belongs to\n Platform text not null, -- 'fcm' | 'apns'\n Token text not null, -- opaque platform device/registration token\n UpdatedAt int null, -- epoch ms; strictly increases per self-update (replay guard)\n Sig text null, -- ed25519 self-sig over (PeerId|Platform|Token|UpdatedAt)\n constraint AuthorizedInsert check on insert, delete (\n -- Authorized by an owner key (membership vouch), exactly like CadrePeer.\n -- The single-field digest frames the base58btc PeerId as TEXT.\n exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(coalesce(new.PeerId, old.PeerId)), context.Signature, A.Key, 'ed25519'))\n ),\n constraint AuthorizedUpdate check on update (\n -- Peer self-updates its own token, signing with its OWN ed25519 key (the key\n -- behind PeerId). PeerId is immutable, UpdatedAt must strictly increase (replay\n -- guard), and Sig is verified against the stored CadrePeer.PublicKey over the\n -- signed payload (cadre-core device-token.ts:deviceTokenSignedPayload). Platform\n -- and Token may change on self-update (platform switch / reinstall / rotation).\n (\n new.PeerId = old.PeerId\n and new.UpdatedAt > coalesce(old.UpdatedAt, 0)\n and exists (select 1 from CadrePeer P where P.PeerId = new.PeerId and verify(\n digest(new.PeerId || '|' || new.Platform || '|' || new.Token || '|' || cast(new.UpdatedAt as text)),\n new.Sig, P.PublicKey, 'ed25519'))\n )\n -- or an owner re-authorizes (rotation / correction)\n or exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.PeerId), context.Signature, A.Key, 'ed25519'))\n )\n ) with context (OwnerKey text null, Signature text);\n\n -- An open invitation to form a strand with this party\n table FormationInvite (\n Token text primary key, -- Just a random string\n sAppId text, -- The app for the strand that will be formed\n ExpiresAt datetime null,\n TotalUses int null check (TotalUses >= 0),\n ValidationUrl text null, -- Web hook - send disclosure, IP address...\n StrandId text null, -- Host strand this invite binds to (provision-then-record). Non-null => the responder records consent against this pre-existing strand; null => legacy responder-provisions path.\n StampId text not null unique, -- single-use authorization nonce (anti-replay)\n constraint AuthorizedAddOrRemove check on insert, delete (\n -- Authorized by an owner signing over THIS row\n -- (Token, sAppId, ExpiresAt, TotalUses, ValidationUrl, StrandId, StampId); single-use via unique StampId.\n -- coalesce(new.F, old.F) binds the NEW row on insert and the OLD row on delete (cf. CadrePeer).\n -- StrandId is a nullable bound field: it signs as '' when absent (cf. ValidationUrl).\n exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(\n digest(\n coalesce(new.Token, old.Token),\n coalesce(new.sAppId, old.sAppId),\n coalesce(cast(coalesce(new.ExpiresAt, old.ExpiresAt) as text), ''),\n coalesce(cast(coalesce(new.TotalUses, old.TotalUses) as text), ''),\n coalesce(coalesce(new.ValidationUrl, old.ValidationUrl), ''),\n coalesce(coalesce(new.StrandId, old.StrandId), ''),\n coalesce(new.StampId, old.StampId)\n ),\n context.Signature, A.Key, 'ed25519'))\n ),\n -- Invites are insert/delete only: forbid in-place mutation so the row-bound\n -- AuthorizedAddOrRemove signature cannot be sidestepped by updating consent\n -- parameters (TotalUses/ExpiresAt/...) after a legitimate insert. A bare check\n -- defaults to insert+update; the check on insert, delete above excludes update,\n -- so this guard is required. Mirrors FormationUsage.InsertOnly.\n constraint Immutable check on update (false)\n ) with context (OwnerKey text, Signature text);\n\n table FormationUsage (\n Token text,\n UseNumber int,\n Disclosure text,\n StrandId text,\n primary key (Token, UseNumber),\n constraint InsertOnly check on update, delete (false),\n constraint Monotonic check (\n -- Read the PRE-transaction snapshot (committed.*): this CHECK auto-defers\n -- to commit (it has a subquery), by which point the row being inserted is\n -- live, so a plain from-FormationUsage would count this row itself and make\n -- UseNumber = max+1 unsatisfiable. committed.* excludes the in-flight row,\n -- giving sequential 1,2,3... per token (cross-transaction; concurrent\n -- redemptions of the same token collide on the (Token, UseNumber) PK).\n new.UseNumber = coalesce((select max(UseNumber) from committed.FormationUsage U where U.Token = new.Token), 0) + 1\n ),\n constraint Authorized check on insert (\n -- Satisfies an invitation\n exists (\n select 1 from FormationInvite FI\n where FI.Token = new.Token\n and (FI.TotalUses is null or FI.TotalUses >= new.UseNumber)\n and (FI.ExpiresAt is null or FI.ExpiresAt > context.Now)\n and (FI.ValidationUrl is null or verify(digest(new.Token || new.Disclosure), context.ValidationSignature, context.ValidationKey, 'ed25519'))\n )\n ),\n constraint StrandExists check (exists (select 1 from Strand S where S.Id = new.StrandId)),\n ) with context (PeerId text, PeerSignature text, Now datetime, ValidationKey text null, ValidationSignature text null);\n}\n\napply schema CadreControl;";
|
package/dist/control-schema.js
CHANGED
|
@@ -12,30 +12,30 @@
|
|
|
12
12
|
export const CONTROL_SCHEMA = `-- This manages a Sereus party's cadre, or set of nodes, and their participation in strands (networks)
|
|
13
13
|
declare schema CadreControl {
|
|
14
14
|
-- A key that can authorize various control changes
|
|
15
|
-
table
|
|
15
|
+
table OwnerKey (
|
|
16
16
|
Key text primary key,
|
|
17
17
|
StampId text not null unique, -- single-use authorization nonce (anti-replay)
|
|
18
18
|
constraint Authorized check (
|
|
19
|
-
-- Bootstrap: first
|
|
20
|
-
(select count(1) from
|
|
19
|
+
-- Bootstrap: first owner key needs no existing authorization
|
|
20
|
+
(select count(1) from OwnerKey) <= 1
|
|
21
21
|
|
|
22
|
-
-- Old
|
|
23
|
-
or (old.Key is not null and old.Key = context.
|
|
22
|
+
-- Old owner authorizes by signing over THIS row (Key, StampId); single-use via unique StampId
|
|
23
|
+
or (old.Key is not null and old.Key = context.OwnerKey and verify(digest(new.Key, new.StampId), context.Signature, old.Key, 'ed25519'))
|
|
24
24
|
|
|
25
|
-
-- or other
|
|
26
|
-
or exists (select 1 from
|
|
25
|
+
-- or other owners authorize by signing over THIS row (Key, StampId); single-use via unique StampId
|
|
26
|
+
or exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.Key, new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
27
27
|
)
|
|
28
|
-
) with context (
|
|
28
|
+
) with context (OwnerKey text null, Signature text null);
|
|
29
29
|
|
|
30
30
|
-- A key that can validate a strand formation disclosure
|
|
31
31
|
table ValidationKey (
|
|
32
32
|
Key text primary key,
|
|
33
33
|
StampId text not null unique, -- single-use authorization nonce (anti-replay)
|
|
34
34
|
constraint Authorized check (
|
|
35
|
-
--
|
|
36
|
-
exists (select 1 from
|
|
35
|
+
-- Owners authorize by signing over THIS row (Key, StampId); single-use via unique StampId
|
|
36
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.Key, new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
37
37
|
)
|
|
38
|
-
) with context (
|
|
38
|
+
) with context (OwnerKey text, Signature text);
|
|
39
39
|
|
|
40
40
|
-- A network of members sharing an sApp database, each contributing peer nodes (their cadre) to the overall cohort
|
|
41
41
|
-- Cadre peers should participate in each of these strands
|
|
@@ -45,8 +45,8 @@ declare schema CadreControl {
|
|
|
45
45
|
Type text, -- Types: 'o' = Open, 'c' = Closed -- Open can still control writes in the sApp, but only Closed controls reads
|
|
46
46
|
StampId text not null unique, -- single-use authorization nonce (anti-replay)
|
|
47
47
|
constraint Authorized check (
|
|
48
|
-
-- Authorized by an
|
|
49
|
-
exists (select 1 from
|
|
48
|
+
-- Authorized by an owner signing over THIS row (Id, Type, MemberPrivateKey, StampId); single-use via unique StampId
|
|
49
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.Id, new.Type, coalesce(new.MemberPrivateKey, ''), new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
50
50
|
|
|
51
51
|
-- or authorized by a cadre peer who has received a valid formation invitation.
|
|
52
52
|
-- That path has no signed writer yet; when one is built it must still supply a
|
|
@@ -58,7 +58,7 @@ declare schema CadreControl {
|
|
|
58
58
|
-- member key. A non-null MemberPrivateKey requires a closed strand ('c').
|
|
59
59
|
new.MemberPrivateKey is null or new.Type = 'c'
|
|
60
60
|
)
|
|
61
|
-
) with context (
|
|
61
|
+
) with context (OwnerKey text, Signature text);
|
|
62
62
|
|
|
63
63
|
-- A peer (node) that is part of the cadre, carrying a self-published,
|
|
64
64
|
-- freshness-stamped, self-signed address record (see PeerAddressRecord in cadre-core).
|
|
@@ -70,31 +70,55 @@ declare schema CadreControl {
|
|
|
70
70
|
Multiaddr text, -- comma-joined current addrs (signaling / p2p-circuit first)
|
|
71
71
|
UpdatedAt int null, -- epoch ms; strictly increases per self-update (replay/rollback guard)
|
|
72
72
|
Sig text null, -- ed25519 self-signature over the signed payload (base64url); null until self-published
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
StampId text not null unique, -- single-use authorization nonce (anti-replay), bound into the voucher/remove digests; rotates on (re)insert
|
|
74
|
+
VouchOwner text null, -- ed25519 (base64url) owner key that vouched this membership (== insert context.OwnerKey)
|
|
75
|
+
VouchSig text null, -- that owner's signature over digest(PeerId, StampId) (== insert context.Signature). A reader checks VouchOwner against its NODE-LOCAL trusted-owner anchor (not this replicated table, which a self-owner can pollute) to decide authorized membership.
|
|
76
|
+
constraint AuthorizedInsert check on insert (
|
|
77
|
+
-- Authorized by an owner key, which vouches both membership and the
|
|
75
78
|
-- PublicKey<->PeerId binding (cadre-core derives PublicKey from PeerId before insert).
|
|
76
|
-
-- The
|
|
77
|
-
-- cadre-core peer-authorization.ts:
|
|
78
|
-
exists (select 1 from
|
|
79
|
+
-- The digest binds PeerId + the single-use StampId nonce (two TEXT fields),
|
|
80
|
+
-- matching cadre-core peer-authorization.ts:cadrePeerVoucherDigest.
|
|
81
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.PeerId, new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
82
|
+
-- Persist the vouching (owner, signature) onto the row so a reader can later
|
|
83
|
+
-- re-check VouchOwner against its node-local anchor. The stored pair MUST equal
|
|
84
|
+
-- the pair the verify above validated, so a writer cannot store a voucher it did
|
|
85
|
+
-- not actually receive a signature for.
|
|
86
|
+
and new.VouchOwner = context.OwnerKey
|
|
87
|
+
and new.VouchSig = context.Signature
|
|
88
|
+
),
|
|
89
|
+
constraint AuthorizedDelete check on delete (
|
|
90
|
+
-- Delete is authorized by a signature over a DISTINCT 'remove'-scoped digest bound
|
|
91
|
+
-- to the row's StampId (cadre-core peer-authorization.ts:cadrePeerRemoveDigest), so
|
|
92
|
+
-- the stored voucher (a signature over digest(PeerId, StampId)) can NEVER be replayed
|
|
93
|
+
-- to authorize a delete. The remove signature rides in context and is never stored.
|
|
94
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(old.PeerId, old.StampId, 'remove'), context.Signature, A.Key, 'ed25519'))
|
|
79
95
|
),
|
|
80
96
|
constraint AuthorizedUpdate check on update (
|
|
81
97
|
-- Peer self-updates its own addrs + freshness, signing with its OWN ed25519 key
|
|
82
|
-
-- (the key behind PeerId). PeerId/PublicKey are immutable on
|
|
83
|
-
-- must strictly increase (replay guard), and Sig is verified
|
|
84
|
-
-- PublicKey over the same payload the publish path signs
|
|
98
|
+
-- (the key behind PeerId). PeerId/PublicKey/StampId/voucher are immutable on
|
|
99
|
+
-- self-update, UpdatedAt must strictly increase (replay guard), and Sig is verified
|
|
100
|
+
-- against the stored PublicKey over the same payload the publish path signs
|
|
85
101
|
-- (cadre-core peer-record.ts:peerRecordSignedPayload).
|
|
86
102
|
(
|
|
87
103
|
new.PeerId = old.PeerId
|
|
88
104
|
and new.PublicKey = old.PublicKey
|
|
105
|
+
and new.StampId = old.StampId
|
|
106
|
+
and new.VouchOwner = old.VouchOwner
|
|
107
|
+
and new.VouchSig = old.VouchSig
|
|
89
108
|
and new.UpdatedAt > coalesce(old.UpdatedAt, 0)
|
|
90
109
|
and verify(
|
|
91
110
|
digest(new.PeerId || '|' || new.Multiaddr || '|' || cast(new.UpdatedAt as text)),
|
|
92
111
|
new.Sig, new.PublicKey, 'ed25519')
|
|
93
112
|
)
|
|
94
|
-
-- or an
|
|
95
|
-
|
|
113
|
+
-- or an owner re-authorizes (rotation / correction): re-vouches the row over
|
|
114
|
+
-- its current StampId, so the stored voucher is re-bound to the re-authorizing owner.
|
|
115
|
+
or (
|
|
116
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.PeerId, new.StampId), context.Signature, A.Key, 'ed25519'))
|
|
117
|
+
and new.VouchOwner = context.OwnerKey
|
|
118
|
+
and new.VouchSig = context.Signature
|
|
119
|
+
)
|
|
96
120
|
)
|
|
97
|
-
) with context (
|
|
121
|
+
) with context (OwnerKey text null, Signature text);
|
|
98
122
|
|
|
99
123
|
-- A mobile cadre peer's platform push token (FCM/APNs), self-published so a
|
|
100
124
|
-- server peer can deliver a push-wake to a suspended app over the platform push
|
|
@@ -109,9 +133,9 @@ declare schema CadreControl {
|
|
|
109
133
|
UpdatedAt int null, -- epoch ms; strictly increases per self-update (replay guard)
|
|
110
134
|
Sig text null, -- ed25519 self-sig over (PeerId|Platform|Token|UpdatedAt)
|
|
111
135
|
constraint AuthorizedInsert check on insert, delete (
|
|
112
|
-
-- Authorized by an
|
|
136
|
+
-- Authorized by an owner key (membership vouch), exactly like CadrePeer.
|
|
113
137
|
-- The single-field digest frames the base58btc PeerId as TEXT.
|
|
114
|
-
exists (select 1 from
|
|
138
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(coalesce(new.PeerId, old.PeerId)), context.Signature, A.Key, 'ed25519'))
|
|
115
139
|
),
|
|
116
140
|
constraint AuthorizedUpdate check on update (
|
|
117
141
|
-- Peer self-updates its own token, signing with its OWN ed25519 key (the key
|
|
@@ -126,10 +150,10 @@ declare schema CadreControl {
|
|
|
126
150
|
digest(new.PeerId || '|' || new.Platform || '|' || new.Token || '|' || cast(new.UpdatedAt as text)),
|
|
127
151
|
new.Sig, P.PublicKey, 'ed25519'))
|
|
128
152
|
)
|
|
129
|
-
-- or an
|
|
130
|
-
or exists (select 1 from
|
|
153
|
+
-- or an owner re-authorizes (rotation / correction)
|
|
154
|
+
or exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(digest(new.PeerId), context.Signature, A.Key, 'ed25519'))
|
|
131
155
|
)
|
|
132
|
-
) with context (
|
|
156
|
+
) with context (OwnerKey text null, Signature text);
|
|
133
157
|
|
|
134
158
|
-- An open invitation to form a strand with this party
|
|
135
159
|
table FormationInvite (
|
|
@@ -141,11 +165,11 @@ declare schema CadreControl {
|
|
|
141
165
|
StrandId text null, -- Host strand this invite binds to (provision-then-record). Non-null => the responder records consent against this pre-existing strand; null => legacy responder-provisions path.
|
|
142
166
|
StampId text not null unique, -- single-use authorization nonce (anti-replay)
|
|
143
167
|
constraint AuthorizedAddOrRemove check on insert, delete (
|
|
144
|
-
-- Authorized by an
|
|
168
|
+
-- Authorized by an owner signing over THIS row
|
|
145
169
|
-- (Token, sAppId, ExpiresAt, TotalUses, ValidationUrl, StrandId, StampId); single-use via unique StampId.
|
|
146
170
|
-- coalesce(new.F, old.F) binds the NEW row on insert and the OLD row on delete (cf. CadrePeer).
|
|
147
171
|
-- StrandId is a nullable bound field: it signs as '' when absent (cf. ValidationUrl).
|
|
148
|
-
exists (select 1 from
|
|
172
|
+
exists (select 1 from OwnerKey A where A.Key = context.OwnerKey and verify(
|
|
149
173
|
digest(
|
|
150
174
|
coalesce(new.Token, old.Token),
|
|
151
175
|
coalesce(new.sAppId, old.sAppId),
|
|
@@ -163,7 +187,7 @@ declare schema CadreControl {
|
|
|
163
187
|
-- defaults to insert+update; the check on insert, delete above excludes update,
|
|
164
188
|
-- so this guard is required. Mirrors FormationUsage.InsertOnly.
|
|
165
189
|
constraint Immutable check on update (false)
|
|
166
|
-
) with context (
|
|
190
|
+
) with context (OwnerKey text, Signature text);
|
|
167
191
|
|
|
168
192
|
table FormationUsage (
|
|
169
193
|
Token text,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-schema.js","sourceRoot":"","sources":["../src/control-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG
|
|
1
|
+
{"version":3,"file":"control-schema.js","sourceRoot":"","sources":["../src/control-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAkNH,CAAC"}
|
package/dist/device-token.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export declare function deviceTokenSignedPayload(record: Omit<DeviceTokenRecord,
|
|
|
36
36
|
* Returns a fully-populated {@link DeviceTokenRecord}.
|
|
37
37
|
*
|
|
38
38
|
* @param fields - the record fields to sign
|
|
39
|
-
* @param privateKeyB64 - base64url ed25519 seed (see `
|
|
39
|
+
* @param privateKeyB64 - base64url ed25519 seed (see `ed25519KeyPairFromLibp2p`)
|
|
40
40
|
*/
|
|
41
41
|
export declare function signDeviceTokenRecord(fields: Omit<DeviceTokenRecord, 'sig'>, privateKeyB64: string): DeviceTokenRecord;
|
|
42
42
|
/**
|
package/dist/device-token.js
CHANGED
|
@@ -39,7 +39,7 @@ export function deviceTokenSignedPayload(record) {
|
|
|
39
39
|
* Returns a fully-populated {@link DeviceTokenRecord}.
|
|
40
40
|
*
|
|
41
41
|
* @param fields - the record fields to sign
|
|
42
|
-
* @param privateKeyB64 - base64url ed25519 seed (see `
|
|
42
|
+
* @param privateKeyB64 - base64url ed25519 seed (see `ed25519KeyPairFromLibp2p`)
|
|
43
43
|
*/
|
|
44
44
|
export function signDeviceTokenRecord(fields, privateKeyB64) {
|
|
45
45
|
const payloadDigest = deviceTokenSignedPayload(fields);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { PrivateKey } from '@libp2p/interface';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* An Ed25519 keypair expressed in the base64url form that
|
|
4
4
|
* `@optimystic/quereus-plugin-crypto` (`sign`/`verify`/`getPublicKey`) consumes.
|
|
5
|
+
* Generic bridge type — reused for owner keys, member keys, and any other
|
|
6
|
+
* seed→public keypair the control/strand databases sign with.
|
|
5
7
|
*/
|
|
6
|
-
export interface
|
|
8
|
+
export interface Ed25519KeyPair {
|
|
7
9
|
/** 32-byte Ed25519 seed, base64url-encoded — the crypto-plugin private key. */
|
|
8
10
|
privateKeyB64: string;
|
|
9
11
|
/** 32-byte Ed25519 public key, base64url-encoded. */
|
|
@@ -11,31 +13,30 @@ export interface AuthorityKeyPair {
|
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Bridge a libp2p Ed25519 private key into the base64url keypair used by the
|
|
14
|
-
* control-database
|
|
16
|
+
* control- and strand-database signing constraints.
|
|
15
17
|
*
|
|
16
18
|
* libp2p stores an Ed25519 private key as 64 raw bytes: the first 32 are the
|
|
17
19
|
* seed (the actual scalar source), the last 32 are the public key — see
|
|
18
20
|
* `@libp2p/crypto`'s `Ed25519PrivateKey`. `@optimystic/quereus-plugin-crypto`
|
|
19
21
|
* (via `@noble/curves`) treats the 32-byte seed *as* the private key and
|
|
20
22
|
* derives the public key from it with standard Ed25519. The two derivations
|
|
21
|
-
* agree, so the node's peer identity and its
|
|
23
|
+
* agree, so the node's peer identity and its signing key are one keypair:
|
|
22
24
|
* `getPublicKey(privateKeyB64)` === `publicKeyB64`.
|
|
23
25
|
*
|
|
24
26
|
* @param privateKey - The node's libp2p Ed25519 private key.
|
|
25
|
-
* @returns The base64url seed/public-key pair for
|
|
27
|
+
* @returns The base64url seed/public-key pair for signing operations.
|
|
26
28
|
* @throws If the key is not Ed25519 or the raw bytes aren't the expected length.
|
|
27
29
|
*/
|
|
28
|
-
export declare function
|
|
30
|
+
export declare function ed25519KeyPairFromLibp2p(privateKey: PrivateKey): Ed25519KeyPair;
|
|
29
31
|
/**
|
|
30
32
|
* Derive the base64url Ed25519 public key from a base64url 32-byte private seed
|
|
31
33
|
* — the same derivation the seed-bootstrap signer uses internally
|
|
32
34
|
* (`SeedBootstrapService` constructor). Use this to enroll a standalone
|
|
33
|
-
* (non-libp2p)
|
|
34
|
-
*
|
|
35
|
-
* authority key is *not* the node's peer identity (so `authorityKeyFromLibp2p`,
|
|
35
|
+
* (non-libp2p) key into the control DB before minting an invite, when the
|
|
36
|
+
* key is *not* the node's peer identity (so `ed25519KeyPairFromLibp2p`,
|
|
36
37
|
* which needs a libp2p key object, does not apply).
|
|
37
38
|
*
|
|
38
39
|
* @param privateKeyB64 - The base64url-encoded 32-byte Ed25519 seed.
|
|
39
40
|
* @returns The base64url-encoded Ed25519 public key.
|
|
40
41
|
*/
|
|
41
|
-
export declare function
|
|
42
|
+
export declare function ed25519PublicKeyFromPrivate(privateKeyB64: string): string;
|
|
@@ -2,23 +2,23 @@ import { toString as uint8ArrayToString } from 'uint8arrays';
|
|
|
2
2
|
import { getPublicKey } from '@optimystic/quereus-plugin-crypto';
|
|
3
3
|
/**
|
|
4
4
|
* Bridge a libp2p Ed25519 private key into the base64url keypair used by the
|
|
5
|
-
* control-database
|
|
5
|
+
* control- and strand-database signing constraints.
|
|
6
6
|
*
|
|
7
7
|
* libp2p stores an Ed25519 private key as 64 raw bytes: the first 32 are the
|
|
8
8
|
* seed (the actual scalar source), the last 32 are the public key — see
|
|
9
9
|
* `@libp2p/crypto`'s `Ed25519PrivateKey`. `@optimystic/quereus-plugin-crypto`
|
|
10
10
|
* (via `@noble/curves`) treats the 32-byte seed *as* the private key and
|
|
11
11
|
* derives the public key from it with standard Ed25519. The two derivations
|
|
12
|
-
* agree, so the node's peer identity and its
|
|
12
|
+
* agree, so the node's peer identity and its signing key are one keypair:
|
|
13
13
|
* `getPublicKey(privateKeyB64)` === `publicKeyB64`.
|
|
14
14
|
*
|
|
15
15
|
* @param privateKey - The node's libp2p Ed25519 private key.
|
|
16
|
-
* @returns The base64url seed/public-key pair for
|
|
16
|
+
* @returns The base64url seed/public-key pair for signing operations.
|
|
17
17
|
* @throws If the key is not Ed25519 or the raw bytes aren't the expected length.
|
|
18
18
|
*/
|
|
19
|
-
export function
|
|
19
|
+
export function ed25519KeyPairFromLibp2p(privateKey) {
|
|
20
20
|
if (privateKey.type !== 'Ed25519') {
|
|
21
|
-
throw new Error(`
|
|
21
|
+
throw new Error(`ed25519KeyPairFromLibp2p requires an Ed25519 key, got ${privateKey.type}`);
|
|
22
22
|
}
|
|
23
23
|
const raw = privateKey.raw;
|
|
24
24
|
if (raw.length !== 64) {
|
|
@@ -38,15 +38,14 @@ export function authorityKeyFromLibp2p(privateKey) {
|
|
|
38
38
|
* Derive the base64url Ed25519 public key from a base64url 32-byte private seed
|
|
39
39
|
* — the same derivation the seed-bootstrap signer uses internally
|
|
40
40
|
* (`SeedBootstrapService` constructor). Use this to enroll a standalone
|
|
41
|
-
* (non-libp2p)
|
|
42
|
-
*
|
|
43
|
-
* authority key is *not* the node's peer identity (so `authorityKeyFromLibp2p`,
|
|
41
|
+
* (non-libp2p) key into the control DB before minting an invite, when the
|
|
42
|
+
* key is *not* the node's peer identity (so `ed25519KeyPairFromLibp2p`,
|
|
44
43
|
* which needs a libp2p key object, does not apply).
|
|
45
44
|
*
|
|
46
45
|
* @param privateKeyB64 - The base64url-encoded 32-byte Ed25519 seed.
|
|
47
46
|
* @returns The base64url-encoded Ed25519 public key.
|
|
48
47
|
*/
|
|
49
|
-
export function
|
|
48
|
+
export function ed25519PublicKeyFromPrivate(privateKeyB64) {
|
|
50
49
|
return getPublicKey(privateKeyB64, 'ed25519', 'base64url', 'base64url');
|
|
51
50
|
}
|
|
52
|
-
//# sourceMappingURL=
|
|
51
|
+
//# sourceMappingURL=ed25519-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ed25519-key.js","sourceRoot":"","sources":["../src/ed25519-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAgBjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAsB;IAC7D,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,yDAAyD,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC;IAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,kEAAkE,GAAG,CAAC,MAAM,QAAQ,CAAC,CAAC;IACxG,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjC,MAAM,YAAY,GAAG,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC;IAC9C,IAAI,YAAY,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,8CAA8C,YAAY,CAAC,MAAM,QAAQ,CAAC,CAAC;IAC7F,CAAC;IAED,OAAO;QACL,aAAa,EAAE,kBAAkB,CAAC,IAAI,EAAE,WAAW,CAAC;QACpD,YAAY,EAAE,kBAAkB,CAAC,YAAY,EAAE,WAAW,CAAC;KAC5D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B,CAAC,aAAqB;IAC/D,OAAO,YAAY,CAAC,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,CAAW,CAAC;AACpF,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from './types.js';
|
|
|
2
2
|
export { canonicalJson } from './canonical-json.js';
|
|
3
3
|
export { CadreNode } from './cadre-node.js';
|
|
4
4
|
export { ControlDatabase, buildAuthorizationMessage, type ControlDatabaseConfig, type ControlTable } from './control-database.js';
|
|
5
|
-
export {
|
|
5
|
+
export { ed25519KeyPairFromLibp2p, ed25519PublicKeyFromPrivate, type Ed25519KeyPair } from './ed25519-key.js';
|
|
6
6
|
export { InMemoryKeyStore, KeyStoreAccessError, DEFAULT_IDENTITY_KEY_ID, type KeyStore, type KeyId } from './key-store.js';
|
|
7
7
|
export { StrandDatabase, type StrandDatabaseConfig } from './strand-database.js';
|
|
8
8
|
export { StrandWatcher, type StrandWatcherCallbacks, type StrandQueryable, type SAppIdLookup, type StrandRowWithApp } from './strand-watcher.js';
|
|
@@ -13,11 +13,11 @@ export { EnrollmentService, type MemberVerifier, type MemberRegistry } from './e
|
|
|
13
13
|
export { StrandSolicitationService, createDefaultFormationResponseValidator, type DisclosureValidator, type FormationUsageRecorder, type ResolvedHostStrand, type StrandProvisioner, type FormationSigner, type FormationResponseValidator, type StrandSolicitationServiceOptions } from './strand-solicitation.js';
|
|
14
14
|
export { ControlFormationUsageRecorder } from './control-formation-recorder.js';
|
|
15
15
|
export { generateStrandMemberKey, strandMemberKeyPair } from './strand-member-key.js';
|
|
16
|
-
export { signStrandPayload, verifyStrandPayload, bootstrapFounderMembership, issueInvite, consumeInvite,
|
|
16
|
+
export { signStrandPayload, verifyStrandPayload, bootstrapFounderMembership, issueInvite, consumeInvite, addMemberByManager, registerMemberPeer, addManager, removeManager, STRAND_ENGINE, STRAND_ENGINE_VERSION, type FounderBootstrapParams, type IssueInviteParams, type IssuedInvite, type ConsumeInviteParams, type AddMemberByManagerParams, type RegisterMemberPeerParams, type AddManagerParams, type RemoveManagerParams } from './strand-membership-writer.js';
|
|
17
17
|
export { canonicalDatetime } from './canonical-datetime.js';
|
|
18
18
|
export { StrandMemberRegistry, StrandMemberVerifier, memberRegistrationPayload, type StrandAdmission } from './strand-member-registry.js';
|
|
19
19
|
export { SeedBootstrapService, SEED_PROTOCOL, ed25519PublicKeyB64FromPeerId, type SeedBootstrapConfig, type SeedEventCallbacks } from './seed-bootstrap.js';
|
|
20
|
-
export { peerAuthorizationDigest, verifyPeerAuthorization } from './peer-authorization.js';
|
|
20
|
+
export { peerAuthorizationDigest, cadrePeerVoucherDigest, cadrePeerRemoveDigest, verifyPeerAuthorization } from './peer-authorization.js';
|
|
21
21
|
export { StrandWakeService, dialWake, WAKE_PROTOCOL, type StrandWakeServiceOptions, type DialWakeOptions } from './strand-wake-protocol.js';
|
|
22
22
|
export { StrandAddrService, collectStrandAddrs, STRAND_ADDR_PROTOCOL, type StrandAddrServiceOptions, type StrandAddrPeer, type CollectStrandAddrsOptions } from './strand-addr-protocol.js';
|
|
23
23
|
export { peerRecordSignedPayload, signPeerRecord, verifyPeerRecordSignature, isPeerRecordFresh, isSignalingAddr, orderSignalingFirst, currentMemberTrustPolicy, DEFAULT_PEER_RECORD_MAX_AGE_MS, DEFAULT_PEER_RECORD_HEARTBEAT_MS } from './peer-record.js';
|
package/dist/index.js
CHANGED
|
@@ -6,9 +6,9 @@ export { canonicalJson } from './canonical-json.js';
|
|
|
6
6
|
export { CadreNode } from './cadre-node.js';
|
|
7
7
|
// Control database
|
|
8
8
|
export { ControlDatabase, buildAuthorizationMessage } from './control-database.js';
|
|
9
|
-
//
|
|
10
|
-
export {
|
|
11
|
-
// Pluggable key store (backend-agnostic identity/
|
|
9
|
+
// Ed25519 key bridge (libp2p Ed25519 -> base64url keypair)
|
|
10
|
+
export { ed25519KeyPairFromLibp2p, ed25519PublicKeyFromPrivate } from './ed25519-key.js';
|
|
11
|
+
// Pluggable key store (backend-agnostic identity/owner key material seam).
|
|
12
12
|
// Dependency-free: the interface, error, default slot id, and in-memory backend
|
|
13
13
|
// are safe in every (RN/browser/Node) entry graph. The Node FileKeyStore is a
|
|
14
14
|
// separate subpath module ('@serfab/cadre-core/key-store-file') so its node:fs
|
|
@@ -30,12 +30,12 @@ export { StrandSolicitationService, createDefaultFormationResponseValidator } fr
|
|
|
30
30
|
// DB-backed FormationUsageRecorder (reads/writes the real CadreControl tables)
|
|
31
31
|
export { ControlFormationUsageRecorder } from './control-formation-recorder.js';
|
|
32
32
|
// Closed-strand member key generation (ed25519 protobuf, base64) + the
|
|
33
|
-
// protobuf->base64url bridge that derives the founding Member/
|
|
33
|
+
// protobuf->base64url bridge that derives the founding Member/Manager key.
|
|
34
34
|
export { generateStrandMemberKey, strandMemberKeyPair } from './strand-member-key.js';
|
|
35
35
|
// Strand membership writer (founder bootstrap, invite issuance/consumption,
|
|
36
|
-
//
|
|
36
|
+
// manager-admit, member-peer registration, manager rotation, + shared signing
|
|
37
37
|
// primitives reused across the flows).
|
|
38
|
-
export { signStrandPayload, verifyStrandPayload, bootstrapFounderMembership, issueInvite, consumeInvite,
|
|
38
|
+
export { signStrandPayload, verifyStrandPayload, bootstrapFounderMembership, issueInvite, consumeInvite, addMemberByManager, registerMemberPeer, addManager, removeManager, STRAND_ENGINE, STRAND_ENGINE_VERSION } from './strand-membership-writer.js';
|
|
39
39
|
// Engine-canonical datetime helper (shared by control + strand signed-write flows
|
|
40
40
|
// so a signed timestamp byte-matches the datetime-coerced column the CHECK sees).
|
|
41
41
|
export { canonicalDatetime } from './canonical-datetime.js';
|
|
@@ -44,8 +44,8 @@ export { canonicalDatetime } from './canonical-datetime.js';
|
|
|
44
44
|
export { StrandMemberRegistry, StrandMemberVerifier, memberRegistrationPayload } from './strand-member-registry.js';
|
|
45
45
|
// Seed Bootstrap
|
|
46
46
|
export { SeedBootstrapService, SEED_PROTOCOL, ed25519PublicKeyB64FromPeerId } from './seed-bootstrap.js';
|
|
47
|
-
// Peer Authorization (shared
|
|
48
|
-
export { peerAuthorizationDigest, verifyPeerAuthorization } from './peer-authorization.js';
|
|
47
|
+
// Peer Authorization (shared owner-signature digest + offline verifier)
|
|
48
|
+
export { peerAuthorizationDigest, cadrePeerVoucherDigest, cadrePeerRemoveDigest, verifyPeerAuthorization } from './peer-authorization.js';
|
|
49
49
|
// Strand Wake (control-network push-wake protocol)
|
|
50
50
|
export { StrandWakeService, dialWake, WAKE_PROTOCOL } from './strand-wake-protocol.js';
|
|
51
51
|
// Strand Address (control-network strand-address RPC) — wire types come via
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,cAAc,YAAY,CAAC;AAE3B,+DAA+D;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,uBAAuB;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,mBAAmB;AACnB,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAiD,MAAM,uBAAuB,CAAC;AAElI,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/key-store.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Backend-agnostic store for raw private key material.
|
|
3
3
|
*
|
|
4
4
|
* Mobile cadre nodes hold sensitive key material at rest: the libp2p
|
|
5
|
-
* peer/node identity key and (in the single-key reference model) the
|
|
5
|
+
* peer/node identity key and (in the single-key reference model) the owner
|
|
6
6
|
* signing key derived from it. This module defines the seam those keys flow
|
|
7
7
|
* through so a platform-secure backend (iOS Keychain / Android Keystore) can be
|
|
8
8
|
* plugged in without `@serfab/cadre-core` taking any platform dependency.
|
package/dist/key-store.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Backend-agnostic store for raw private key material.
|
|
3
3
|
*
|
|
4
4
|
* Mobile cadre nodes hold sensitive key material at rest: the libp2p
|
|
5
|
-
* peer/node identity key and (in the single-key reference model) the
|
|
5
|
+
* peer/node identity key and (in the single-key reference model) the owner
|
|
6
6
|
* signing key derived from it. This module defines the seam those keys flow
|
|
7
7
|
* through so a platform-secure backend (iOS Keychain / Android Keystore) can be
|
|
8
8
|
* plugged in without `@serfab/cadre-core` taking any platform dependency.
|
|
@@ -1,26 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical digest an
|
|
2
|
+
* Canonical digest an owner signs to authorize a peer.
|
|
3
3
|
*
|
|
4
4
|
* This is the exact byte construction {@link SeedBootstrapService.authorizePeer}
|
|
5
5
|
* signs over when it inserts an authorized `CadrePeer` row:
|
|
6
6
|
* `digest([peerId], 'sha256', 'base64url')`. Factored into one place so the
|
|
7
|
-
* producer (
|
|
7
|
+
* producer (owner signing) and the verifier (the offline `cadre enroll
|
|
8
8
|
* register` check) can never drift apart — change the digest here and both move
|
|
9
9
|
* together. The SQL mirror is the bare single-field `digest(coalesce(new.PeerId, old.PeerId))`.
|
|
10
10
|
*/
|
|
11
11
|
export declare function peerAuthorizationDigest(peerId: string): string;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Canonical digest an owner signs to VOUCH a `CadrePeer` membership row (insert
|
|
14
|
+
* and the owner re-touch update). Binds the peer id to the row's single-use
|
|
15
|
+
* `StampId` nonce, so a captured signed insert cannot be replayed (the `StampId` is
|
|
16
|
+
* unique) and — because {@link cadrePeerRemoveDigest} scopes a DIFFERENT payload — the
|
|
17
|
+
* stored voucher (`VouchSig`) cannot be replayed to authorize a delete.
|
|
18
|
+
*
|
|
19
|
+
* The SQL mirror is `digest(new.PeerId, new.StampId)` (two TEXT fields), matching the
|
|
20
|
+
* canonical multi-field framing in control-database.ts:buildAuthorizationMessage.
|
|
21
|
+
*/
|
|
22
|
+
export declare function cadrePeerVoucherDigest(peerId: string, stampId: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Canonical digest an owner signs to REMOVE a `CadrePeer` row. Deliberately a
|
|
25
|
+
* distinct payload from {@link cadrePeerVoucherDigest} (adds the `'remove'` action
|
|
26
|
+
* tag) so the row's stored voucher — a signature over the voucher digest — can never
|
|
27
|
+
* satisfy this delete check. The signature is supplied in write context and never
|
|
28
|
+
* stored, so no reader can replay it; binding the live row's `StampId` also invalidates
|
|
29
|
+
* any captured remove after a delete+reinsert (the nonce rotates).
|
|
30
|
+
*
|
|
31
|
+
* SQL mirror: `digest(old.PeerId, old.StampId, 'remove')`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function cadrePeerRemoveDigest(peerId: string, stampId: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Verify that `signature` is a valid owner ed25519 signature over `peerId`'s
|
|
36
|
+
* authorization digest, using `ownerPublicKey` (base64url).
|
|
15
37
|
*
|
|
16
38
|
* This is the mirror of the signing done in
|
|
17
39
|
* {@link SeedBootstrapService.authorizePeer}: it checks the signature against
|
|
18
40
|
* {@link peerAuthorizationDigest}. A `true` result means the holder of the
|
|
19
|
-
*
|
|
41
|
+
* owner private key vouched for this peer ID — it does NOT mean the peer is
|
|
20
42
|
* registered anywhere.
|
|
21
43
|
*
|
|
22
44
|
* Returns a boolean and never throws: malformed base64url, a bad/garbage key, or
|
|
23
45
|
* any crypto failure resolves to `false` (callers want a verdict, not an
|
|
24
46
|
* exception). The catch is logged at debug.
|
|
25
47
|
*/
|
|
26
|
-
export declare function verifyPeerAuthorization(peerId: string,
|
|
48
|
+
export declare function verifyPeerAuthorization(peerId: string, ownerPublicKey: string, signature: string): boolean;
|