@did-btcr2/aggregation 0.1.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/LICENSE +373 -0
- package/README.md +19 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/browser.js +24017 -0
- package/dist/browser.mjs +24011 -0
- package/dist/cjs/index.js +5690 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/beacon-strategy.js +76 -0
- package/dist/esm/beacon-strategy.js.map +1 -0
- package/dist/esm/cohort.js +316 -0
- package/dist/esm/cohort.js.map +1 -0
- package/dist/esm/conditions.js +102 -0
- package/dist/esm/conditions.js.map +1 -0
- package/dist/esm/errors.js +22 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/fallback-spend.js +132 -0
- package/dist/esm/fallback-spend.js.map +1 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logger.js +15 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/messages/base.js +36 -0
- package/dist/esm/messages/base.js.map +1 -0
- package/dist/esm/messages/bodies.js +149 -0
- package/dist/esm/messages/bodies.js.map +1 -0
- package/dist/esm/messages/constants.js +37 -0
- package/dist/esm/messages/constants.js.map +1 -0
- package/dist/esm/messages/factories.js +139 -0
- package/dist/esm/messages/factories.js.map +1 -0
- package/dist/esm/messages/guards.js +40 -0
- package/dist/esm/messages/guards.js.map +1 -0
- package/dist/esm/messages/index.js +6 -0
- package/dist/esm/messages/index.js.map +1 -0
- package/dist/esm/participant.js +588 -0
- package/dist/esm/participant.js.map +1 -0
- package/dist/esm/phases.js +45 -0
- package/dist/esm/phases.js.map +1 -0
- package/dist/esm/recovery-policy.js +156 -0
- package/dist/esm/recovery-policy.js.map +1 -0
- package/dist/esm/recovery-spend.js +106 -0
- package/dist/esm/recovery-spend.js.map +1 -0
- package/dist/esm/runner/aggregation-runner.js +75 -0
- package/dist/esm/runner/aggregation-runner.js.map +1 -0
- package/dist/esm/runner/events.js +2 -0
- package/dist/esm/runner/events.js.map +1 -0
- package/dist/esm/runner/index.js +6 -0
- package/dist/esm/runner/index.js.map +1 -0
- package/dist/esm/runner/participant-runner.js +407 -0
- package/dist/esm/runner/participant-runner.js.map +1 -0
- package/dist/esm/runner/service-runner.js +701 -0
- package/dist/esm/runner/service-runner.js.map +1 -0
- package/dist/esm/runner/typed-emitter.js +80 -0
- package/dist/esm/runner/typed-emitter.js.map +1 -0
- package/dist/esm/service.js +774 -0
- package/dist/esm/service.js.map +1 -0
- package/dist/esm/signer.js +33 -0
- package/dist/esm/signer.js.map +1 -0
- package/dist/esm/signing-session.js +195 -0
- package/dist/esm/signing-session.js.map +1 -0
- package/dist/esm/transport/didcomm.js +41 -0
- package/dist/esm/transport/didcomm.js.map +1 -0
- package/dist/esm/transport/error.js +12 -0
- package/dist/esm/transport/error.js.map +1 -0
- package/dist/esm/transport/factory.js +29 -0
- package/dist/esm/transport/factory.js.map +1 -0
- package/dist/esm/transport/http/client.js +342 -0
- package/dist/esm/transport/http/client.js.map +1 -0
- package/dist/esm/transport/http/envelope.js +126 -0
- package/dist/esm/transport/http/envelope.js.map +1 -0
- package/dist/esm/transport/http/errors.js +11 -0
- package/dist/esm/transport/http/errors.js.map +1 -0
- package/dist/esm/transport/http/inbox-buffer.js +45 -0
- package/dist/esm/transport/http/inbox-buffer.js.map +1 -0
- package/dist/esm/transport/http/index.js +12 -0
- package/dist/esm/transport/http/index.js.map +1 -0
- package/dist/esm/transport/http/nonce-cache.js +38 -0
- package/dist/esm/transport/http/nonce-cache.js.map +1 -0
- package/dist/esm/transport/http/protocol.js +28 -0
- package/dist/esm/transport/http/protocol.js.map +1 -0
- package/dist/esm/transport/http/rate-limiter.js +45 -0
- package/dist/esm/transport/http/rate-limiter.js.map +1 -0
- package/dist/esm/transport/http/request-auth.js +100 -0
- package/dist/esm/transport/http/request-auth.js.map +1 -0
- package/dist/esm/transport/http/server.js +475 -0
- package/dist/esm/transport/http/server.js.map +1 -0
- package/dist/esm/transport/http/sse-stream.js +110 -0
- package/dist/esm/transport/http/sse-stream.js.map +1 -0
- package/dist/esm/transport/http/sse-writer.js +25 -0
- package/dist/esm/transport/http/sse-writer.js.map +1 -0
- package/dist/esm/transport/in-memory.js +149 -0
- package/dist/esm/transport/in-memory.js.map +1 -0
- package/dist/esm/transport/index.js +8 -0
- package/dist/esm/transport/index.js.map +1 -0
- package/dist/esm/transport/nostr.js +491 -0
- package/dist/esm/transport/nostr.js.map +1 -0
- package/dist/esm/transport/transport.js +2 -0
- package/dist/esm/transport/transport.js.map +1 -0
- package/dist/types/beacon-strategy.d.ts +56 -0
- package/dist/types/beacon-strategy.d.ts.map +1 -0
- package/dist/types/cohort.d.ts +188 -0
- package/dist/types/cohort.d.ts.map +1 -0
- package/dist/types/conditions.d.ts +86 -0
- package/dist/types/conditions.d.ts.map +1 -0
- package/dist/types/errors.d.ts +14 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/fallback-spend.d.ts +94 -0
- package/dist/types/fallback-spend.d.ts.map +1 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/logger.d.ts +22 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/messages/base.d.ts +58 -0
- package/dist/types/messages/base.d.ts.map +1 -0
- package/dist/types/messages/bodies.d.ts +177 -0
- package/dist/types/messages/bodies.d.ts.map +1 -0
- package/dist/types/messages/constants.d.ts +31 -0
- package/dist/types/messages/constants.d.ts.map +1 -0
- package/dist/types/messages/factories.d.ts +217 -0
- package/dist/types/messages/factories.d.ts.map +1 -0
- package/dist/types/messages/guards.d.ts +11 -0
- package/dist/types/messages/guards.d.ts.map +1 -0
- package/dist/types/messages/index.d.ts +6 -0
- package/dist/types/messages/index.d.ts.map +1 -0
- package/dist/types/participant.d.ts +162 -0
- package/dist/types/participant.d.ts.map +1 -0
- package/dist/types/phases.d.ts +55 -0
- package/dist/types/phases.d.ts.map +1 -0
- package/dist/types/recovery-policy.d.ts +132 -0
- package/dist/types/recovery-policy.d.ts.map +1 -0
- package/dist/types/recovery-spend.d.ts +94 -0
- package/dist/types/recovery-spend.d.ts.map +1 -0
- package/dist/types/runner/aggregation-runner.d.ts +72 -0
- package/dist/types/runner/aggregation-runner.d.ts.map +1 -0
- package/dist/types/runner/events.d.ts +147 -0
- package/dist/types/runner/events.d.ts.map +1 -0
- package/dist/types/runner/index.d.ts +6 -0
- package/dist/types/runner/index.d.ts.map +1 -0
- package/dist/types/runner/participant-runner.d.ts +122 -0
- package/dist/types/runner/participant-runner.d.ts.map +1 -0
- package/dist/types/runner/service-runner.d.ts +228 -0
- package/dist/types/runner/service-runner.d.ts.map +1 -0
- package/dist/types/runner/typed-emitter.d.ts +41 -0
- package/dist/types/runner/typed-emitter.d.ts.map +1 -0
- package/dist/types/service.d.ts +177 -0
- package/dist/types/service.d.ts.map +1 -0
- package/dist/types/signer.d.ts +50 -0
- package/dist/types/signer.d.ts.map +1 -0
- package/dist/types/signing-session.d.ts +81 -0
- package/dist/types/signing-session.d.ts.map +1 -0
- package/dist/types/transport/didcomm.d.ts +23 -0
- package/dist/types/transport/didcomm.d.ts.map +1 -0
- package/dist/types/transport/error.d.ts +8 -0
- package/dist/types/transport/error.d.ts.map +1 -0
- package/dist/types/transport/factory.d.ts +28 -0
- package/dist/types/transport/factory.d.ts.map +1 -0
- package/dist/types/transport/http/client.d.ts +56 -0
- package/dist/types/transport/http/client.d.ts.map +1 -0
- package/dist/types/transport/http/envelope.d.ts +64 -0
- package/dist/types/transport/http/envelope.d.ts.map +1 -0
- package/dist/types/transport/http/errors.d.ts +9 -0
- package/dist/types/transport/http/errors.d.ts.map +1 -0
- package/dist/types/transport/http/inbox-buffer.d.ts +32 -0
- package/dist/types/transport/http/inbox-buffer.d.ts.map +1 -0
- package/dist/types/transport/http/index.d.ts +12 -0
- package/dist/types/transport/http/index.d.ts.map +1 -0
- package/dist/types/transport/http/nonce-cache.d.ts +26 -0
- package/dist/types/transport/http/nonce-cache.d.ts.map +1 -0
- package/dist/types/transport/http/protocol.d.ts +53 -0
- package/dist/types/transport/http/protocol.d.ts.map +1 -0
- package/dist/types/transport/http/rate-limiter.d.ts +41 -0
- package/dist/types/transport/http/rate-limiter.d.ts.map +1 -0
- package/dist/types/transport/http/request-auth.d.ts +50 -0
- package/dist/types/transport/http/request-auth.d.ts.map +1 -0
- package/dist/types/transport/http/server.d.ts +118 -0
- package/dist/types/transport/http/server.d.ts.map +1 -0
- package/dist/types/transport/http/sse-stream.d.ts +34 -0
- package/dist/types/transport/http/sse-stream.d.ts.map +1 -0
- package/dist/types/transport/http/sse-writer.d.ts +12 -0
- package/dist/types/transport/http/sse-writer.d.ts.map +1 -0
- package/dist/types/transport/in-memory.d.ts +64 -0
- package/dist/types/transport/in-memory.d.ts.map +1 -0
- package/dist/types/transport/index.d.ts +8 -0
- package/dist/types/transport/index.d.ts.map +1 -0
- package/dist/types/transport/nostr.d.ts +153 -0
- package/dist/types/transport/nostr.d.ts.map +1 -0
- package/dist/types/transport/transport.d.ts +62 -0
- package/dist/types/transport/transport.d.ts.map +1 -0
- package/package.json +128 -0
- package/src/beacon-strategy.ts +142 -0
- package/src/cohort.ts +422 -0
- package/src/conditions.ts +172 -0
- package/src/errors.ts +25 -0
- package/src/fallback-spend.ts +223 -0
- package/src/index.ts +23 -0
- package/src/logger.ts +33 -0
- package/src/messages/base.ts +80 -0
- package/src/messages/bodies.ts +328 -0
- package/src/messages/constants.ts +40 -0
- package/src/messages/factories.ts +297 -0
- package/src/messages/guards.ts +61 -0
- package/src/messages/index.ts +5 -0
- package/src/participant.ts +779 -0
- package/src/phases.ts +91 -0
- package/src/recovery-policy.ts +237 -0
- package/src/recovery-spend.ts +192 -0
- package/src/runner/aggregation-runner.ts +125 -0
- package/src/runner/events.ts +129 -0
- package/src/runner/index.ts +5 -0
- package/src/runner/participant-runner.ts +488 -0
- package/src/runner/service-runner.ts +891 -0
- package/src/runner/typed-emitter.ts +87 -0
- package/src/service.ts +1000 -0
- package/src/signer.ts +67 -0
- package/src/signing-session.ts +290 -0
- package/src/transport/didcomm.ts +59 -0
- package/src/transport/error.ts +13 -0
- package/src/transport/factory.ts +65 -0
- package/src/transport/http/client.ts +409 -0
- package/src/transport/http/envelope.ts +204 -0
- package/src/transport/http/errors.ts +11 -0
- package/src/transport/http/inbox-buffer.ts +53 -0
- package/src/transport/http/index.ts +11 -0
- package/src/transport/http/nonce-cache.ts +43 -0
- package/src/transport/http/protocol.ts +57 -0
- package/src/transport/http/rate-limiter.ts +75 -0
- package/src/transport/http/request-auth.ts +164 -0
- package/src/transport/http/server.ts +617 -0
- package/src/transport/http/sse-stream.ts +121 -0
- package/src/transport/http/sse-writer.ts +23 -0
- package/src/transport/in-memory.ts +178 -0
- package/src/transport/index.ts +7 -0
- package/src/transport/nostr.ts +576 -0
- package/src/transport/transport.ts +79 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
2
|
+
import type { BaseMessage } from '../messages/base.js';
|
|
3
|
+
import type { MessageHandler, Transport } from './transport.js';
|
|
4
|
+
/**
|
|
5
|
+
* In-process message bus connecting one or more {@link InMemoryTransport}
|
|
6
|
+
* instances. Routes broadcasts to every registered actor and directed messages
|
|
7
|
+
* to the actor that owns the recipient DID, with no relay, server, or network.
|
|
8
|
+
*
|
|
9
|
+
* Each delivery does a JSON round-trip (Uint8Array preserved as `__bytes` hex)
|
|
10
|
+
* so handlers receive an isolated, serialization-faithful copy, exactly as a
|
|
11
|
+
* real transport would. The message `body` is merged to the top level to match
|
|
12
|
+
* the shape the {@link NostrTransport} dispatch produces.
|
|
13
|
+
*
|
|
14
|
+
* @class InMemoryBus
|
|
15
|
+
*/
|
|
16
|
+
export declare class InMemoryBus {
|
|
17
|
+
#private;
|
|
18
|
+
/** Attach a transport to this bus. Called by the transport's constructor. */
|
|
19
|
+
register(transport: InMemoryTransport): void;
|
|
20
|
+
/** Detach a transport from this bus. */
|
|
21
|
+
unregister(transport: InMemoryTransport): void;
|
|
22
|
+
/**
|
|
23
|
+
* Deliver a message. With no `recipient` the message is broadcast to every
|
|
24
|
+
* actor on the bus; otherwise it is routed to the single transport that owns
|
|
25
|
+
* the recipient DID.
|
|
26
|
+
*/
|
|
27
|
+
deliver(message: BaseMessage, _sender: string, recipient?: string): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* In-process {@link Transport} that routes aggregation messages through an
|
|
31
|
+
* {@link InMemoryBus} instead of a relay or HTTP server. Supports multiple
|
|
32
|
+
* actors per instance, so a single transport can host both a service and its
|
|
33
|
+
* participants (e.g. a cohort-of-one via {@link AggregationRunner.solo}).
|
|
34
|
+
*
|
|
35
|
+
* Encryption is a no-op (in-process, same trust domain); `registerPeer` /
|
|
36
|
+
* `getPeerPk` keep a registry so the contract matches the wire transports.
|
|
37
|
+
*
|
|
38
|
+
* @class InMemoryTransport
|
|
39
|
+
* @implements {Transport}
|
|
40
|
+
*/
|
|
41
|
+
export declare class InMemoryTransport implements Transport {
|
|
42
|
+
#private;
|
|
43
|
+
name: string;
|
|
44
|
+
readonly bus: InMemoryBus;
|
|
45
|
+
/** @param bus Shared bus. Pass the same bus to connect multiple transports. */
|
|
46
|
+
constructor(bus?: InMemoryBus);
|
|
47
|
+
start(): void;
|
|
48
|
+
registerActor(did: string, keys: SchnorrKeyPair): void;
|
|
49
|
+
getActorPk(did: string): Uint8Array | undefined;
|
|
50
|
+
/** True if `did` is registered on this transport. Used by the bus for routing. */
|
|
51
|
+
hasActor(did: string): boolean;
|
|
52
|
+
registerPeer(did: string, communicationPk: Uint8Array): void;
|
|
53
|
+
getPeerPk(did: string): Uint8Array | undefined;
|
|
54
|
+
registerMessageHandler(actorDid: string, messageType: string, handler: MessageHandler): void;
|
|
55
|
+
unregisterMessageHandler(actorDid: string, messageType: string): void;
|
|
56
|
+
unregisterActor(did: string): void;
|
|
57
|
+
sendMessage(message: BaseMessage, sender: string, recipient?: string): Promise<void>;
|
|
58
|
+
publishRepeating(message: BaseMessage, sender: string, intervalMs: number, recipient?: string): () => void;
|
|
59
|
+
/** Deliver a broadcast message to every actor on this transport that handles `type`. */
|
|
60
|
+
dispatchBroadcast(type: string, message: unknown): Promise<void>;
|
|
61
|
+
/** Deliver a directed message to the recipient actor's handler for `type`. */
|
|
62
|
+
dispatchDirected(recipientDid: string, type: string, message: unknown): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=in-memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory.d.ts","sourceRoot":"","sources":["../../../src/transport/in-memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAShE;;;;;;;;;;;GAWG;AACH,qBAAa,WAAW;;IAGtB,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAI5C,wCAAwC;IACxC,UAAU,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAI9C;;;;OAIG;IACG,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA0BxF;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,YAAW,SAAS;;IACjD,IAAI,EAAE,MAAM,CAAe;IAC3B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAK1B,+EAA+E;gBACnE,GAAG,GAAE,WAA+B;IAKhD,KAAK,IAAI,IAAI;IAIb,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI;IAOtD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI/C,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI9B,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,UAAU,GAAG,IAAI;IAI5D,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI9C,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAK5F,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAKrE,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAQ5B,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI1F,gBAAgB,CACd,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,IAAI;IAcb,wFAAwF;IAClF,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtE,8EAA8E;IACxE,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAI5F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/transport/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { Did } from '@did-btcr2/common';
|
|
2
|
+
import type { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
3
|
+
import { SimplePool } from 'nostr-tools/pool';
|
|
4
|
+
import type { Logger } from '../logger.js';
|
|
5
|
+
import type { BaseMessage } from '../messages/base.js';
|
|
6
|
+
import type { MessageHandler, Transport } from './transport.js';
|
|
7
|
+
/**
|
|
8
|
+
* Default Nostr relay URLs.
|
|
9
|
+
* @constant {Array<string>} DEFAULT_NOSTR_RELAYS
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_NOSTR_RELAYS: string[];
|
|
12
|
+
export interface NostrTransportConfig {
|
|
13
|
+
relays?: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Optional logger for transport-level diagnostics (publish/subscribe events,
|
|
16
|
+
* relay rejections, parse failures). Defaults to {@link CONSOLE_LOGGER}.
|
|
17
|
+
*/
|
|
18
|
+
logger?: Logger;
|
|
19
|
+
/**
|
|
20
|
+
* How far back (in milliseconds) to set the `since` filter on the broadcast
|
|
21
|
+
* (COHORT_ADVERT) subscription. Some public relays do NOT replay historical
|
|
22
|
+
* events to late subscribers when the filter has no `since`, so the advert
|
|
23
|
+
* gets lost if the subscription lands after the publish. A short lookback
|
|
24
|
+
* window nudges those relays into delivering recent adverts. Set to 0 to
|
|
25
|
+
* disable the filter entirely (unbounded history). Defaults to
|
|
26
|
+
* {@link DEFAULT_BROADCAST_LOOKBACK_MS} (5 minutes).
|
|
27
|
+
*/
|
|
28
|
+
broadcastLookbackMs?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Default `since` lookback for broadcast (COHORT_ADVERT) subscriptions: 5 minutes. */
|
|
31
|
+
export declare const DEFAULT_BROADCAST_LOOKBACK_MS: number;
|
|
32
|
+
/**
|
|
33
|
+
* Nostr Transport for did:btcr2 aggregation messages.
|
|
34
|
+
*
|
|
35
|
+
* A single NostrTransport manages one relay pool and supports multiple
|
|
36
|
+
* registered actors. Each actor registers its own DID and keys via
|
|
37
|
+
* {@link registerActor}; the transport resolves the correct identity when
|
|
38
|
+
* sending or receiving.
|
|
39
|
+
*
|
|
40
|
+
* Message routing:
|
|
41
|
+
* - Keygen messages (COHORT_ADVERT, COHORT_OPT_IN, COHORT_OPT_IN_ACCEPT, COHORT_READY) to kind 1 (plaintext)
|
|
42
|
+
* - Update messages (SUBMIT_UPDATE, DISTRIBUTE_AGGREGATED_DATA, VALIDATION_ACK) to kind 1059 (NIP-44 encrypted)
|
|
43
|
+
* - Sign messages to kind 1059 (NIP-44 encrypted)
|
|
44
|
+
*
|
|
45
|
+
* @class NostrTransport
|
|
46
|
+
* @implements {Transport}
|
|
47
|
+
*/
|
|
48
|
+
export declare class NostrTransport implements Transport {
|
|
49
|
+
#private;
|
|
50
|
+
name: string;
|
|
51
|
+
pool?: SimplePool;
|
|
52
|
+
constructor(config?: NostrTransportConfig);
|
|
53
|
+
/**
|
|
54
|
+
* Registers an actor (DID + keys) to send/receive messages with.
|
|
55
|
+
* Must be called before start() to ensure subscriptions are created for the actor.
|
|
56
|
+
* @param {string} did - The DID of the actor.
|
|
57
|
+
* @param {SchnorrKeyPair} keys - The Schnorr key pair for the actor.
|
|
58
|
+
* @throws {TransportAdapterError} If the actor is already registered or if the transport has already started.
|
|
59
|
+
* @example
|
|
60
|
+
* const transport = new NostrTransport();
|
|
61
|
+
* const keys = SchnorrKeyPair.generate();
|
|
62
|
+
* const did = DidBtcr2.create(keys.publicKey.compressed, { idType: 'KEY', network: 'mutinynet' });
|
|
63
|
+
* transport.registerActor(did, keys);
|
|
64
|
+
* transport.start();
|
|
65
|
+
*/
|
|
66
|
+
registerActor(did: string, keys: SchnorrKeyPair): void;
|
|
67
|
+
/**
|
|
68
|
+
* Detach an actor: drop its handlers, close its relay subscriptions, and remove its keys + peer
|
|
69
|
+
* mapping. Idempotent.
|
|
70
|
+
* @param {string} did - The DID of the actor to unregister.
|
|
71
|
+
* @returns {void}
|
|
72
|
+
* @throws {TransportAdapterError} If the transport is not started or if the pool is unavailable.
|
|
73
|
+
* @example
|
|
74
|
+
* transport.unregisterActor(did);
|
|
75
|
+
*/
|
|
76
|
+
unregisterActor(did: string): void;
|
|
77
|
+
/**
|
|
78
|
+
* Remove a single (actor, messageType) handler. Idempotent.
|
|
79
|
+
* @param {string} actorDid - The DID of the actor.
|
|
80
|
+
* @param {string} messageType - The type of message to unregister the handler for.
|
|
81
|
+
* @returns {void}
|
|
82
|
+
* @example
|
|
83
|
+
* transport.unregisterMessageHandler(actorDid, messageType);
|
|
84
|
+
*/
|
|
85
|
+
unregisterMessageHandler(actorDid: string, messageType: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* Gets the public key for a registered actor by their DID.
|
|
88
|
+
* @param {string} did - The DID of the registered actor to get the public key for.
|
|
89
|
+
* @returns {Uint8Array | undefined} The compressed public key bytes for the actor's DID, or
|
|
90
|
+
* undefined if the DID is not registered.
|
|
91
|
+
*/
|
|
92
|
+
getActorPk(did: string): Uint8Array | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Registers a peer's communication public key for encrypted messages.
|
|
95
|
+
* @param {string} did - The DID of the peer to register.
|
|
96
|
+
* @param {Uint8Array} communicationPk - The compressed secp256k1 public key bytes for the peer.
|
|
97
|
+
*/
|
|
98
|
+
registerPeer(did: string, communicationPk: Uint8Array): void;
|
|
99
|
+
/**
|
|
100
|
+
* Gets the registered communication public key for a peer by their DID.
|
|
101
|
+
* @param {string} did - The DID of the peer to get the communication public key for.
|
|
102
|
+
* @returns {Uint8Array | undefined} The compressed secp256k1 public key bytes for the peer, or
|
|
103
|
+
* undefined if the peer is not registered.
|
|
104
|
+
*/
|
|
105
|
+
getPeerPk(did: string): Uint8Array | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* Registers a message handler function for a specific actor and message type. The handler will be called
|
|
108
|
+
* when a message of the specified type is received for the actor's DID. The transport must have been
|
|
109
|
+
* started for handlers to be invoked. If the transport is already started, the handler will be registered
|
|
110
|
+
* immediately; otherwise, it will be registered when the transport starts and the actor's subscription is created.
|
|
111
|
+
* @param {string} actorDid - The DID of the actor to register the message handler for.
|
|
112
|
+
* @param {string} messageType - The type of message to handle.
|
|
113
|
+
* @param {MessageHandler} handler - The function to handle incoming messages of the specified type.
|
|
114
|
+
* @throws {TransportAdapterError} If the actor DID is not registered or if the handler is invalid.
|
|
115
|
+
*/
|
|
116
|
+
registerMessageHandler(actorDid: string, messageType: string, handler: MessageHandler): void;
|
|
117
|
+
/**
|
|
118
|
+
* Starts the transport by connecting to the configured Nostr relays and setting up subscriptions
|
|
119
|
+
* for all registered actors. This method must be called after registering actors via registerActor()
|
|
120
|
+
* and before sending or receiving messages. The transport will subscribe to broadcast events (kind 1)
|
|
121
|
+
* for cohort adverts and directed events (kinds 1 and 1059) for each registered actor based on their
|
|
122
|
+
* public keys. Incoming events are processed and dispatched to the appropriate handlers based on
|
|
123
|
+
* message type. If the transport is already started, this method has no effect.
|
|
124
|
+
* @returns {NostrTransport}
|
|
125
|
+
*/
|
|
126
|
+
start(): NostrTransport;
|
|
127
|
+
/**
|
|
128
|
+
* Sends a message by publishing a Nostr event to the configured relays. The message is serialized
|
|
129
|
+
* as JSON and included in the event content.
|
|
130
|
+
* @param {BaseMessage} message - The aggregation message to send. Must include a valid `type` property.
|
|
131
|
+
* @param {Did} sender - The DID of the registered actor sending the message. Must have been
|
|
132
|
+
* registered via registerActor().
|
|
133
|
+
* @param {Did} [to] - Optional recipient DID for directed messages. Required for encrypted message
|
|
134
|
+
* types. If provided, must have been registered via registerPeer().
|
|
135
|
+
* @returns {Promise<void>} Resolves when the message has been published to the relays. Note that
|
|
136
|
+
* publication is best-effort: the method will resolve as long as at least one relay accepts the
|
|
137
|
+
* event, even if others reject it.
|
|
138
|
+
*/
|
|
139
|
+
sendMessage(message: BaseMessage, sender: Did, to?: Did): Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* Publish the message once immediately and then re-publish on an interval
|
|
142
|
+
* until the returned stop function is invoked.
|
|
143
|
+
*
|
|
144
|
+
* Useful for broadcast messages (COHORT_ADVERT) on relays that don't
|
|
145
|
+
* backfill historical events to late subscribers: republishing gives late
|
|
146
|
+
* joiners a window to discover the message without requiring protocol
|
|
147
|
+
* changes. Relay rate-limit / publish failures inside the interval are
|
|
148
|
+
* caught and logged rather than propagated - the caller should stop the
|
|
149
|
+
* repeater once the protocol condition is satisfied.
|
|
150
|
+
*/
|
|
151
|
+
publishRepeating(message: BaseMessage, sender: Did, intervalMs: number, recipient?: Did): () => void;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=nostr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nostr.d.ts","sourceRoot":"","sources":["../../../src/transport/nostr.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAMzD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAIvD,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhE;;;GAGG;AACH,eAAO,MAAM,oBAAoB,UAKhC,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,uFAAuF;AACvF,eAAO,MAAM,6BAA6B,QAAgB,CAAC;AAU3D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,cAAe,YAAW,SAAS;;IAC9C,IAAI,EAAE,MAAM,CAAW;IAEvB,IAAI,CAAC,EAAE,UAAU,CAAC;gBAQN,MAAM,CAAC,EAAE,oBAAoB;IAMzC;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI;IAUtD;;;;;;;;OAQG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAYlC;;;;;;;OAOG;IACH,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAMrE;;;;;OAKG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI/C;;;;OAIG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,UAAU,GAAG,IAAI;IAY5D;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI9C;;;;;;;;;OASG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI;IAW5F;;;;;;;;OAQG;IACH,KAAK,IAAI,cAAc;IAsCvB;;;;;;;;;;;OAWG;IACG,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAmF7E;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,GAAG,GAAG,MAAM,IAAI;CAwNrG"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
2
|
+
import type { BaseMessage } from '../messages/base.js';
|
|
3
|
+
export type SyncMessageHandler = (msg: any) => void;
|
|
4
|
+
export type AsyncMessageHandler = (msg: any) => Promise<void>;
|
|
5
|
+
export type MessageHandler = SyncMessageHandler | AsyncMessageHandler;
|
|
6
|
+
export type TransportType = 'nostr' | 'didcomm' | 'http';
|
|
7
|
+
/**
|
|
8
|
+
* Multi-actor message transport.
|
|
9
|
+
*
|
|
10
|
+
* A single transport instance manages one connection (relay pool, channel, etc.)
|
|
11
|
+
* shared by all registered actors. Each actor registers its own DID and keys;
|
|
12
|
+
* the transport resolves the correct identity when sending or receiving messages.
|
|
13
|
+
*
|
|
14
|
+
* The transport is a pure passthrough: it knows nothing about the aggregation
|
|
15
|
+
* protocol. It only signs/encrypts outgoing messages with the sender's keys and
|
|
16
|
+
* dispatches incoming messages to the correct actor's registered handler.
|
|
17
|
+
*
|
|
18
|
+
* @interface Transport
|
|
19
|
+
*/
|
|
20
|
+
export interface Transport {
|
|
21
|
+
name: string;
|
|
22
|
+
/** Start the underlying transport (idempotent, only starts once). */
|
|
23
|
+
start(): void;
|
|
24
|
+
/** Register an actor (service or participant) with this transport. */
|
|
25
|
+
registerActor(did: string, keys: SchnorrKeyPair): void;
|
|
26
|
+
/** Return a registered actor's compressed communication public key. */
|
|
27
|
+
getActorPk(did: string): Uint8Array | undefined;
|
|
28
|
+
/** Store a remote peer's communication public key for encrypted routing. */
|
|
29
|
+
registerPeer(did: string, communicationPk: Uint8Array): void;
|
|
30
|
+
/** Retrieve a remote peer's communication public key. */
|
|
31
|
+
getPeerPk(did: string): Uint8Array | undefined;
|
|
32
|
+
/** Register a message handler scoped to a specific actor. */
|
|
33
|
+
registerMessageHandler(actorDid: string, messageType: string, handler: MessageHandler): void;
|
|
34
|
+
/** Remove a previously-registered handler. No-op if not registered. */
|
|
35
|
+
unregisterMessageHandler(actorDid: string, messageType: string): void;
|
|
36
|
+
/**
|
|
37
|
+
* Detach an actor: unregister all its handlers, drop its keys, and close any
|
|
38
|
+
* transport-level subscriptions created for it. No-op if the actor is not
|
|
39
|
+
* registered.
|
|
40
|
+
*/
|
|
41
|
+
unregisterActor(did: string): void;
|
|
42
|
+
/** Send a message. The transport looks up sender to resolve signing keys. */
|
|
43
|
+
sendMessage(message: BaseMessage, sender: string, recipient?: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Publish the message once immediately and then repeat it on a fixed
|
|
46
|
+
* interval. Returns a stop function the caller MUST invoke when the repeat
|
|
47
|
+
* is no longer needed (e.g. once the protocol state that required the
|
|
48
|
+
* message is satisfied).
|
|
49
|
+
*
|
|
50
|
+
* Useful for broadcasts on transports that don't reliably backfill
|
|
51
|
+
* historical events to late subscribers (many Nostr relays): republishing
|
|
52
|
+
* gives late joiners a window in which to discover the message. The first
|
|
53
|
+
* publish is synchronous-ish (fired before the method returns).
|
|
54
|
+
*
|
|
55
|
+
* Callers specify `recipient` only for directed messages; for broadcasts
|
|
56
|
+
* it is omitted.
|
|
57
|
+
*
|
|
58
|
+
* @returns A stop function. Idempotent, safe to call more than once.
|
|
59
|
+
*/
|
|
60
|
+
publishRepeating(message: BaseMessage, sender: string, intervalMs: number, recipient?: string): () => void;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../../src/transport/transport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;AACpD,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAC9D,MAAM,MAAM,cAAc,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;AAEtE,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IAEb,qEAAqE;IACrE,KAAK,IAAI,IAAI,CAAC;IAEd,sEAAsE;IACtE,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAEvD,uEAAuE;IACvE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAEhD,4EAA4E;IAC5E,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,UAAU,GAAG,IAAI,CAAC;IAE7D,yDAAyD;IACzD,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAE/C,6DAA6D;IAC7D,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAE7F,uEAAuE;IACvE,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtE;;;;OAIG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAEnC,6EAA6E;IAC7E,WAAW,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErF;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CACd,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,IAAI,CAAC;CACf"}
|
package/package.json
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@did-btcr2/aggregation",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Multi-party coordination for did:btcr2 aggregate beacons: sans-I/O AggregationService and AggregationParticipant state machines, MuSig2 (BIP-327) signing sessions, cohort conditions, and pluggable transports (Nostr, HTTP, in-memory).",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/types/index.d.ts",
|
|
12
|
+
"browser": "./dist/browser.mjs",
|
|
13
|
+
"import": "./dist/esm/index.js",
|
|
14
|
+
"require": "./dist/cjs/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./browser": {
|
|
17
|
+
"types": "./dist/types/index.d.ts",
|
|
18
|
+
"default": "./dist/browser.mjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"typesVersions": {
|
|
22
|
+
"*": {
|
|
23
|
+
"browser": [
|
|
24
|
+
"dist/types/index.d.ts"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src"
|
|
31
|
+
],
|
|
32
|
+
"license": "MPL-2.0",
|
|
33
|
+
"contributors": [
|
|
34
|
+
{
|
|
35
|
+
"name": "dcdpr",
|
|
36
|
+
"url": "https://github.com/dcdpr"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "jintekc",
|
|
40
|
+
"url": "https://github.com/jintekc",
|
|
41
|
+
"email": "github@jintek.consulting"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"homepage": "https://github.com/dcdpr/did-btcr2-js/tree/main/packages/aggregation",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+ssh://git@github.com:dcdpr/did-btcr2-js.git",
|
|
48
|
+
"directory": "packages/aggregation"
|
|
49
|
+
},
|
|
50
|
+
"bugs": "https://github.com/dcdpr/did-btcr2-js/issues",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22.0.0"
|
|
56
|
+
},
|
|
57
|
+
"keywords": [
|
|
58
|
+
"did",
|
|
59
|
+
"dids",
|
|
60
|
+
"decentralized identity",
|
|
61
|
+
"decentralized identifiers",
|
|
62
|
+
"did method",
|
|
63
|
+
"did:btcr2",
|
|
64
|
+
"bitcoin",
|
|
65
|
+
"aggregate beacon",
|
|
66
|
+
"aggregation",
|
|
67
|
+
"musig2",
|
|
68
|
+
"bip327",
|
|
69
|
+
"nostr",
|
|
70
|
+
"cohort"
|
|
71
|
+
],
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@noble/curves": "^1.9.7",
|
|
74
|
+
"@noble/hashes": "^1.8.0",
|
|
75
|
+
"@scure/btc-signer": "^1.8.1",
|
|
76
|
+
"nostr-tools": "^2.23.3",
|
|
77
|
+
"@did-btcr2/smt": "^0.3.0",
|
|
78
|
+
"@did-btcr2/keypair": "^0.13.1",
|
|
79
|
+
"@did-btcr2/bitcoin": "^0.7.0",
|
|
80
|
+
"@did-btcr2/cryptosuite": "^8.0.0",
|
|
81
|
+
"@did-btcr2/common": "^9.1.0"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@eslint/js": "^9.20.0",
|
|
85
|
+
"@types/chai": "^5.0.1",
|
|
86
|
+
"@types/chai-as-promised": "^8.0.1",
|
|
87
|
+
"@types/eslint": "^9.6.1",
|
|
88
|
+
"@types/mocha": "^10.0.10",
|
|
89
|
+
"@types/node": "^22.13.4",
|
|
90
|
+
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
|
91
|
+
"@typescript-eslint/parser": "^8.24.1",
|
|
92
|
+
"c8": "^10.1.3",
|
|
93
|
+
"chai": "^5.2.0",
|
|
94
|
+
"chai-as-promised": "^8.0.1",
|
|
95
|
+
"esbuild": "^0.25.0",
|
|
96
|
+
"eslint": "^9.20.1",
|
|
97
|
+
"eslint-plugin-mocha": "^10.5.0",
|
|
98
|
+
"globals": "^15.15.0",
|
|
99
|
+
"mocha": "^11.1.0",
|
|
100
|
+
"mocha-junit-reporter": "^2.2.1",
|
|
101
|
+
"node-stdlib-browser": "^1.3.1",
|
|
102
|
+
"rimraf": "^6.0.1",
|
|
103
|
+
"tsup": "^8.5.1",
|
|
104
|
+
"typedoc-plugin-markdown": "^4.7.0",
|
|
105
|
+
"typescript": "^5.7.3",
|
|
106
|
+
"typescript-eslint": "^8.24.1"
|
|
107
|
+
},
|
|
108
|
+
"scripts": {
|
|
109
|
+
"clean": "rimraf dist coverage tests/compiled",
|
|
110
|
+
"clean:build": "rimraf dist",
|
|
111
|
+
"clean:tests": "rimraf coverage tests/compiled",
|
|
112
|
+
"wipe": "rimraf node_modules pnpm-lock.json",
|
|
113
|
+
"reinstall": "pnpm install --force",
|
|
114
|
+
"build": "pnpm clean:build && pnpm build:esm && pnpm build:cjs && pnpm build:browser",
|
|
115
|
+
"build:esm": "rimraf dist/esm dist/types && pnpm tsc -p tsconfig.json",
|
|
116
|
+
"build:cjs": "tsup && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json",
|
|
117
|
+
"build:browser": "rimraf dist/browser.mjs dist/browser.js && node build/bundles.js",
|
|
118
|
+
"build:tests": "pnpm clean:tests && pnpm tsc -p tests/tsconfig.json",
|
|
119
|
+
"build:all": "pnpm build && pnpm build:tests",
|
|
120
|
+
"release": "pnpm build && pnpm pack && mv *.tgz ../../release/aggregation",
|
|
121
|
+
"lint": "eslint . --max-warnings 0",
|
|
122
|
+
"lint:fix": "eslint . --fix",
|
|
123
|
+
"test": "pnpm c8 mocha",
|
|
124
|
+
"build:test": "pnpm build && pnpm build:tests && pnpm c8 mocha",
|
|
125
|
+
"build:lint:test": "pnpm build && pnpm build:tests && pnpm lint:fix",
|
|
126
|
+
"prepublish": "pnpm build"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { canonicalize } from '@did-btcr2/common';
|
|
2
|
+
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
3
|
+
import type { SerializedSMTProof } from '@did-btcr2/smt';
|
|
4
|
+
import { base64UrlToHash, blockHash, didToIndex, hashToBase64Url, verifySerializedProof } from '@did-btcr2/smt';
|
|
5
|
+
import type { AggregationCohort } from './cohort.js';
|
|
6
|
+
import type { BaseBody } from './messages/base.js';
|
|
7
|
+
|
|
8
|
+
/** Validation result returned to the participant for a distribute-data message. */
|
|
9
|
+
export interface BeaconValidationResult {
|
|
10
|
+
matches: boolean;
|
|
11
|
+
casAnnouncement?: Record<string, string>;
|
|
12
|
+
smtProof?: SerializedSMTProof;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Per-participant body attached to DISTRIBUTE_AGGREGATED_DATA by the service. */
|
|
16
|
+
export interface BeaconDistributePayload {
|
|
17
|
+
casAnnouncement?: Record<string, string>;
|
|
18
|
+
smtProof?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Pluggable strategy for beacon-type-specific aggregation, distribution, and
|
|
23
|
+
* participant-side validation. Lets new beacon types be added without
|
|
24
|
+
* modifying the service or participant state machines: register a new strategy
|
|
25
|
+
* via {@link registerBeaconStrategy}.
|
|
26
|
+
*/
|
|
27
|
+
export interface AggregateBeaconStrategy {
|
|
28
|
+
/** String constant used as `beaconType` on CohortConfig / BaseMessage bodies. */
|
|
29
|
+
readonly type: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Service: build the aggregated data on the cohort after all updates are
|
|
33
|
+
* collected. Implementation should mutate the cohort (set signalBytes,
|
|
34
|
+
* casAnnouncement, smtProofs, etc.).
|
|
35
|
+
*/
|
|
36
|
+
buildAggregatedData(cohort: AggregationCohort): void;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Service: produce the body fields to attach to DISTRIBUTE_AGGREGATED_DATA
|
|
40
|
+
* for a specific participant. Called once per cohort member.
|
|
41
|
+
*/
|
|
42
|
+
getDistributePayload(cohort: AggregationCohort, participantDid: string): BeaconDistributePayload;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Participant: verify the aggregated data they received reflects their own
|
|
46
|
+
* slot. A submitter (`included: true`) checks its update is included; a
|
|
47
|
+
* decliner (`included: false`, cooperative non-inclusion) checks its
|
|
48
|
+
* non-inclusion slot instead (absence from the CAS map, or an SMT
|
|
49
|
+
* non-inclusion proof). `submittedUpdate`/`expectedHash` are present only for a
|
|
50
|
+
* submitter. Pure function: returns matches + sidecar fields for the caller.
|
|
51
|
+
*/
|
|
52
|
+
validateParticipantView(params: {
|
|
53
|
+
participantDid: string;
|
|
54
|
+
included: boolean;
|
|
55
|
+
submittedUpdate?: SignedBTCR2Update;
|
|
56
|
+
expectedHash?: string;
|
|
57
|
+
body: BaseBody;
|
|
58
|
+
}): BeaconValidationResult;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const CAS_STRATEGY: AggregateBeaconStrategy = {
|
|
62
|
+
type : 'CASBeacon',
|
|
63
|
+
|
|
64
|
+
buildAggregatedData(cohort) {
|
|
65
|
+
cohort.buildCASAnnouncement();
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
getDistributePayload(cohort) {
|
|
69
|
+
return { casAnnouncement: cohort.casAnnouncement };
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
validateParticipantView({ participantDid, included, expectedHash, body }) {
|
|
73
|
+
const casAnnouncement = body.casAnnouncement;
|
|
74
|
+
if(!casAnnouncement) return { matches: false };
|
|
75
|
+
if(!included) {
|
|
76
|
+
// Cooperative non-inclusion: the decliner must be ABSENT from the map.
|
|
77
|
+
return { matches: !(participantDid in casAnnouncement), casAnnouncement };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
matches : casAnnouncement[participantDid] === expectedHash,
|
|
81
|
+
casAnnouncement,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const SMT_STRATEGY: AggregateBeaconStrategy = {
|
|
87
|
+
type : 'SMTBeacon',
|
|
88
|
+
|
|
89
|
+
buildAggregatedData(cohort) {
|
|
90
|
+
cohort.buildSMTTree();
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
getDistributePayload(cohort, participantDid) {
|
|
94
|
+
const proof = cohort.smtProofs?.get(participantDid);
|
|
95
|
+
return { smtProof: proof as unknown as Record<string, unknown> | undefined };
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
validateParticipantView({ participantDid, included, submittedUpdate, body }) {
|
|
99
|
+
const smtProof = body.smtProof as unknown as SerializedSMTProof | undefined;
|
|
100
|
+
const index = didToIndex(participantDid);
|
|
101
|
+
|
|
102
|
+
if(!included) {
|
|
103
|
+
// Cooperative non-inclusion: the proof has a nonce but no updateId by
|
|
104
|
+
// construction. The leaf is SHA-256(SHA-256(nonce)); do NOT run the
|
|
105
|
+
// inclusion-only updateId guard (a missing updateId is correct here).
|
|
106
|
+
if(!smtProof?.nonce || smtProof?.updateId) return { matches: false, smtProof };
|
|
107
|
+
const candidateHash = blockHash(blockHash(base64UrlToHash(smtProof.nonce)));
|
|
108
|
+
return { matches: verifySerializedProof(smtProof, index, candidateHash), smtProof };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if(!smtProof?.updateId || !smtProof?.nonce || !submittedUpdate) return { matches: false, smtProof };
|
|
112
|
+
// Verify updateId matches the canonicalized update hash. Proof hash fields
|
|
113
|
+
// are base64url (no padding) per the SMT Proof spec.
|
|
114
|
+
const canonicalBytes = new TextEncoder().encode(canonicalize(submittedUpdate as unknown as Record<string, unknown>));
|
|
115
|
+
const expectedUpdateId = hashToBase64Url(blockHash(canonicalBytes));
|
|
116
|
+
if(smtProof.updateId !== expectedUpdateId) {
|
|
117
|
+
return { matches: false, smtProof };
|
|
118
|
+
}
|
|
119
|
+
// Verify Merkle inclusion. The leaf is SHA-256(SHA-256(nonce) || SHA-256(update)).
|
|
120
|
+
const candidateHash = blockHash(blockHash(base64UrlToHash(smtProof.nonce)), base64UrlToHash(smtProof.updateId));
|
|
121
|
+
return {
|
|
122
|
+
matches : verifySerializedProof(smtProof, index, candidateHash),
|
|
123
|
+
smtProof,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Registered strategies keyed by `beaconType` string. */
|
|
129
|
+
const STRATEGIES: Map<string, AggregateBeaconStrategy> = new Map([
|
|
130
|
+
[CAS_STRATEGY.type, CAS_STRATEGY],
|
|
131
|
+
[SMT_STRATEGY.type, SMT_STRATEGY],
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
/** Register a custom beacon strategy. Overwrites any existing entry with the same type. */
|
|
135
|
+
export function registerBeaconStrategy(strategy: AggregateBeaconStrategy): void {
|
|
136
|
+
STRATEGIES.set(strategy.type, strategy);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Look up a registered beacon strategy by type, or undefined if not registered. */
|
|
140
|
+
export function getBeaconStrategy(type: string): AggregateBeaconStrategy | undefined {
|
|
141
|
+
return STRATEGIES.get(type);
|
|
142
|
+
}
|