@did-btcr2/method 0.28.0 → 0.29.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/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +20092 -31631
- package/dist/browser.mjs +20019 -31558
- package/dist/cjs/index.js +1164 -364
- package/dist/esm/core/aggregation/beacon-strategy.js +62 -0
- package/dist/esm/core/aggregation/beacon-strategy.js.map +1 -0
- package/dist/esm/core/aggregation/cohort.js +31 -8
- package/dist/esm/core/aggregation/cohort.js.map +1 -1
- package/dist/esm/core/aggregation/logger.js +15 -0
- package/dist/esm/core/aggregation/logger.js.map +1 -0
- package/dist/esm/core/aggregation/messages/base.js +12 -1
- package/dist/esm/core/aggregation/messages/base.js.map +1 -1
- package/dist/esm/core/aggregation/messages/bodies.js +90 -0
- package/dist/esm/core/aggregation/messages/bodies.js.map +1 -0
- package/dist/esm/core/aggregation/messages/factories.js.map +1 -1
- package/dist/esm/core/aggregation/messages/index.js +1 -0
- package/dist/esm/core/aggregation/messages/index.js.map +1 -1
- package/dist/esm/core/aggregation/participant.js +39 -46
- package/dist/esm/core/aggregation/participant.js.map +1 -1
- package/dist/esm/core/aggregation/runner/participant-runner.js +33 -7
- package/dist/esm/core/aggregation/runner/participant-runner.js.map +1 -1
- package/dist/esm/core/aggregation/runner/service-runner.js +198 -19
- package/dist/esm/core/aggregation/runner/service-runner.js.map +1 -1
- package/dist/esm/core/aggregation/service.js +143 -15
- package/dist/esm/core/aggregation/service.js.map +1 -1
- package/dist/esm/core/aggregation/signing-session.js +44 -5
- package/dist/esm/core/aggregation/signing-session.js.map +1 -1
- package/dist/esm/core/aggregation/transport/didcomm.js +9 -0
- package/dist/esm/core/aggregation/transport/didcomm.js.map +1 -1
- package/dist/esm/core/aggregation/transport/nostr.js +245 -16
- package/dist/esm/core/aggregation/transport/nostr.js.map +1 -1
- package/dist/esm/core/beacon/beacon.js +147 -61
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/utils.js +14 -9
- package/dist/esm/core/beacon/utils.js.map +1 -1
- package/dist/esm/did-btcr2.js +0 -4
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/did-document.js +2 -2
- package/dist/esm/utils/did-document.js.map +1 -1
- package/dist/types/core/aggregation/beacon-strategy.d.ts +52 -0
- package/dist/types/core/aggregation/beacon-strategy.d.ts.map +1 -0
- package/dist/types/core/aggregation/cohort.d.ts +20 -3
- package/dist/types/core/aggregation/cohort.d.ts.map +1 -1
- package/dist/types/core/aggregation/logger.d.ts +22 -0
- package/dist/types/core/aggregation/logger.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/base.d.ts +13 -1
- package/dist/types/core/aggregation/messages/base.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/bodies.d.ts +130 -0
- package/dist/types/core/aggregation/messages/bodies.d.ts.map +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts +1 -0
- package/dist/types/core/aggregation/messages/factories.d.ts.map +1 -1
- package/dist/types/core/aggregation/messages/index.d.ts +1 -0
- package/dist/types/core/aggregation/messages/index.d.ts.map +1 -1
- package/dist/types/core/aggregation/participant.d.ts +2 -0
- package/dist/types/core/aggregation/participant.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/events.d.ts +32 -6
- package/dist/types/core/aggregation/runner/events.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/participant-runner.d.ts +7 -5
- package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/runner/service-runner.d.ts +33 -3
- package/dist/types/core/aggregation/runner/service-runner.d.ts.map +1 -1
- package/dist/types/core/aggregation/service.d.ts +33 -2
- package/dist/types/core/aggregation/service.d.ts.map +1 -1
- package/dist/types/core/aggregation/signing-session.d.ts +5 -1
- package/dist/types/core/aggregation/signing-session.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/didcomm.d.ts +3 -0
- package/dist/types/core/aggregation/transport/didcomm.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts +99 -1
- package/dist/types/core/aggregation/transport/nostr.d.ts.map +1 -1
- package/dist/types/core/aggregation/transport/transport.d.ts +25 -0
- package/dist/types/core/aggregation/transport/transport.d.ts.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +85 -18
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/utils.d.ts +2 -2
- package/dist/types/core/beacon/utils.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +5 -7
- package/src/core/aggregation/beacon-strategy.ts +123 -0
- package/src/core/aggregation/cohort.ts +34 -8
- package/src/core/aggregation/logger.ts +33 -0
- package/src/core/aggregation/messages/base.ts +20 -5
- package/src/core/aggregation/messages/bodies.ts +223 -0
- package/src/core/aggregation/messages/factories.ts +1 -0
- package/src/core/aggregation/messages/index.ts +1 -0
- package/src/core/aggregation/participant.ts +40 -46
- package/src/core/aggregation/runner/events.ts +27 -3
- package/src/core/aggregation/runner/participant-runner.ts +41 -7
- package/src/core/aggregation/runner/service-runner.ts +227 -19
- package/src/core/aggregation/service.ts +189 -20
- package/src/core/aggregation/signing-session.ts +65 -7
- package/src/core/aggregation/transport/didcomm.ts +17 -0
- package/src/core/aggregation/transport/nostr.ts +266 -23
- package/src/core/aggregation/transport/transport.ts +33 -0
- package/src/core/beacon/beacon.ts +217 -76
- package/src/core/beacon/utils.ts +16 -11
- package/src/did-btcr2.ts +0 -5
- package/src/index.ts +2 -0
- package/src/utils/did-document.ts +2 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal injectable logger for the aggregation subsystem.
|
|
3
|
+
*
|
|
4
|
+
* Each runner and transport adapter accepts a `Logger` option; the default is
|
|
5
|
+
* {@link CONSOLE_LOGGER}, which forwards to `console.*`. Pass
|
|
6
|
+
* {@link SILENT_LOGGER} to suppress output (useful for tests) or a custom
|
|
7
|
+
* implementation to route logs to pino, winston, Sentry, etc.
|
|
8
|
+
*
|
|
9
|
+
* The interface is intentionally small — we don't want production code taking
|
|
10
|
+
* a hard dependency on any specific logger library.
|
|
11
|
+
*/
|
|
12
|
+
export interface Logger {
|
|
13
|
+
debug(message: string, ...args: unknown[]): void;
|
|
14
|
+
info(message: string, ...args: unknown[]): void;
|
|
15
|
+
warn(message: string, ...args: unknown[]): void;
|
|
16
|
+
error(message: string, ...args: unknown[]): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Console-backed logger. Default for runners and transports. */
|
|
20
|
+
export const CONSOLE_LOGGER: Logger = {
|
|
21
|
+
debug : (msg, ...args) => console.debug(msg, ...args),
|
|
22
|
+
info : (msg, ...args) => console.info(msg, ...args),
|
|
23
|
+
warn : (msg, ...args) => console.warn(msg, ...args),
|
|
24
|
+
error : (msg, ...args) => console.error(msg, ...args),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** No-op logger. Useful for tests and production environments with own logging pipeline. */
|
|
28
|
+
export const SILENT_LOGGER: Logger = {
|
|
29
|
+
debug : () => {},
|
|
30
|
+
info : () => {},
|
|
31
|
+
warn : () => {},
|
|
32
|
+
error : () => {},
|
|
33
|
+
};
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current on-the-wire protocol version.
|
|
3
|
+
*
|
|
4
|
+
* Receivers reject messages with an unknown (mismatched) version. Bumping this
|
|
5
|
+
* requires coordinated updates across all participants and any intermediate
|
|
6
|
+
* relays that inspect message content.
|
|
7
|
+
*/
|
|
8
|
+
export const AGGREGATION_WIRE_VERSION = 1;
|
|
9
|
+
|
|
1
10
|
export type BaseBody = {
|
|
2
11
|
cohortId: string;
|
|
3
12
|
cohortSize?: number;
|
|
@@ -10,6 +19,8 @@ export type BaseBody = {
|
|
|
10
19
|
nonceContribution?: Uint8Array;
|
|
11
20
|
partialSignature?: Uint8Array;
|
|
12
21
|
pendingTx?: string;
|
|
22
|
+
/** Hex-encoded scriptPubKey of the UTXO being spent. Required for BIP-341 sighash. */
|
|
23
|
+
prevOutScriptHex?: string;
|
|
13
24
|
prevOutValue?: string;
|
|
14
25
|
communicationPk?: Uint8Array;
|
|
15
26
|
beaconType?: string;
|
|
@@ -23,6 +34,7 @@ export type BaseBody = {
|
|
|
23
34
|
|
|
24
35
|
export type Base = {
|
|
25
36
|
type: string;
|
|
37
|
+
version?: number;
|
|
26
38
|
to?: string;
|
|
27
39
|
from: string;
|
|
28
40
|
body?: BaseBody;
|
|
@@ -30,12 +42,14 @@ export type Base = {
|
|
|
30
42
|
|
|
31
43
|
export class BaseMessage {
|
|
32
44
|
public type: string;
|
|
45
|
+
public version: number;
|
|
33
46
|
public to?: string;
|
|
34
47
|
public from: string;
|
|
35
48
|
public body?: BaseBody;
|
|
36
49
|
|
|
37
|
-
constructor({ type, to, from, body }: Base) {
|
|
50
|
+
constructor({ type, version, to, from, body }: Base) {
|
|
38
51
|
this.type = type;
|
|
52
|
+
this.version = version ?? AGGREGATION_WIRE_VERSION;
|
|
39
53
|
this.to = to;
|
|
40
54
|
this.from = from;
|
|
41
55
|
this.body = body;
|
|
@@ -47,10 +61,11 @@ export class BaseMessage {
|
|
|
47
61
|
*/
|
|
48
62
|
public toJSON(): Base {
|
|
49
63
|
return {
|
|
50
|
-
type
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
type : this.type,
|
|
65
|
+
version : this.version,
|
|
66
|
+
to : this.to,
|
|
67
|
+
from : this.from,
|
|
68
|
+
body : this.body
|
|
54
69
|
};
|
|
55
70
|
}
|
|
56
71
|
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-message-type body interfaces and a discriminated {@link AggregationMessage}
|
|
3
|
+
* union.
|
|
4
|
+
*
|
|
5
|
+
* {@link BaseBody} remains the superset-of-all-fields body type used by the
|
|
6
|
+
* raw {@link BaseMessage} class (see `base.ts`). The narrow interfaces here
|
|
7
|
+
* describe what each specific message type is *required* to carry and are
|
|
8
|
+
* exposed alongside type guards for consumers who want compile-time narrowing.
|
|
9
|
+
*
|
|
10
|
+
* Guards validate both `type` and the presence of required body fields so they
|
|
11
|
+
* are safe to use on messages that have round-tripped through JSON / a relay.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { SerializedSMTProof } from '@did-btcr2/smt';
|
|
15
|
+
import type { BaseMessage } from './base.js';
|
|
16
|
+
import {
|
|
17
|
+
AGGREGATED_NONCE,
|
|
18
|
+
AUTHORIZATION_REQUEST,
|
|
19
|
+
COHORT_ADVERT,
|
|
20
|
+
COHORT_OPT_IN,
|
|
21
|
+
COHORT_OPT_IN_ACCEPT,
|
|
22
|
+
COHORT_READY,
|
|
23
|
+
DISTRIBUTE_AGGREGATED_DATA,
|
|
24
|
+
NONCE_CONTRIBUTION,
|
|
25
|
+
SIGNATURE_AUTHORIZATION,
|
|
26
|
+
SUBMIT_UPDATE,
|
|
27
|
+
VALIDATION_ACK,
|
|
28
|
+
} from './constants.js';
|
|
29
|
+
|
|
30
|
+
// ── Cohort formation (Step 1) ─────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
export interface CohortAdvertBody {
|
|
33
|
+
cohortId: string;
|
|
34
|
+
cohortSize: number;
|
|
35
|
+
beaconType: string;
|
|
36
|
+
network: string;
|
|
37
|
+
communicationPk: Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CohortOptInBody {
|
|
41
|
+
cohortId: string;
|
|
42
|
+
participantPk: Uint8Array;
|
|
43
|
+
communicationPk: Uint8Array;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CohortOptInAcceptBody {
|
|
47
|
+
cohortId: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CohortReadyBody {
|
|
51
|
+
cohortId: string;
|
|
52
|
+
beaconAddress: string;
|
|
53
|
+
cohortKeys: Array<Uint8Array>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Update / aggregation (Steps 2-3) ──────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export interface SubmitUpdateBody {
|
|
59
|
+
cohortId: string;
|
|
60
|
+
signedUpdate: Record<string, unknown>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface DistributeAggregatedDataBody {
|
|
64
|
+
cohortId: string;
|
|
65
|
+
beaconType: string;
|
|
66
|
+
signalBytesHex: string;
|
|
67
|
+
casAnnouncement?: Record<string, string>;
|
|
68
|
+
smtProof?: Record<string, unknown> | SerializedSMTProof;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ValidationAckBody {
|
|
72
|
+
cohortId: string;
|
|
73
|
+
approved: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Signing (Step 4) ──────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
export interface AuthorizationRequestBody {
|
|
79
|
+
cohortId: string;
|
|
80
|
+
sessionId: string;
|
|
81
|
+
pendingTx: string;
|
|
82
|
+
prevOutScriptHex: string;
|
|
83
|
+
prevOutValue: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface NonceContributionBody {
|
|
87
|
+
cohortId: string;
|
|
88
|
+
sessionId: string;
|
|
89
|
+
nonceContribution: Uint8Array;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AggregatedNonceBody {
|
|
93
|
+
cohortId: string;
|
|
94
|
+
sessionId: string;
|
|
95
|
+
aggregatedNonce: Uint8Array;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface SignatureAuthorizationBody {
|
|
99
|
+
cohortId: string;
|
|
100
|
+
sessionId: string;
|
|
101
|
+
partialSignature: Uint8Array;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Narrow message types (BaseMessage & { type, body }) ──────────────────
|
|
105
|
+
|
|
106
|
+
export type CohortAdvertMessage = BaseMessage & { type: typeof COHORT_ADVERT; body: CohortAdvertBody };
|
|
107
|
+
export type CohortOptInMessage = BaseMessage & { type: typeof COHORT_OPT_IN; body: CohortOptInBody };
|
|
108
|
+
export type CohortOptInAcceptMessage = BaseMessage & { type: typeof COHORT_OPT_IN_ACCEPT; body: CohortOptInAcceptBody };
|
|
109
|
+
export type CohortReadyMessage = BaseMessage & { type: typeof COHORT_READY; body: CohortReadyBody };
|
|
110
|
+
export type SubmitUpdateMessage = BaseMessage & { type: typeof SUBMIT_UPDATE; body: SubmitUpdateBody };
|
|
111
|
+
export type DistributeAggregatedDataMessage = BaseMessage & { type: typeof DISTRIBUTE_AGGREGATED_DATA; body: DistributeAggregatedDataBody };
|
|
112
|
+
export type ValidationAckMessage = BaseMessage & { type: typeof VALIDATION_ACK; body: ValidationAckBody };
|
|
113
|
+
export type AuthorizationRequestMessage = BaseMessage & { type: typeof AUTHORIZATION_REQUEST; body: AuthorizationRequestBody };
|
|
114
|
+
export type NonceContributionMessage = BaseMessage & { type: typeof NONCE_CONTRIBUTION; body: NonceContributionBody };
|
|
115
|
+
export type AggregatedNonceMessage = BaseMessage & { type: typeof AGGREGATED_NONCE; body: AggregatedNonceBody };
|
|
116
|
+
export type SignatureAuthorizationMessage = BaseMessage & { type: typeof SIGNATURE_AUTHORIZATION; body: SignatureAuthorizationBody };
|
|
117
|
+
|
|
118
|
+
/** Discriminated union of every well-formed aggregation message. */
|
|
119
|
+
export type AggregationMessage =
|
|
120
|
+
| CohortAdvertMessage
|
|
121
|
+
| CohortOptInMessage
|
|
122
|
+
| CohortOptInAcceptMessage
|
|
123
|
+
| CohortReadyMessage
|
|
124
|
+
| SubmitUpdateMessage
|
|
125
|
+
| DistributeAggregatedDataMessage
|
|
126
|
+
| ValidationAckMessage
|
|
127
|
+
| AuthorizationRequestMessage
|
|
128
|
+
| NonceContributionMessage
|
|
129
|
+
| AggregatedNonceMessage
|
|
130
|
+
| SignatureAuthorizationMessage;
|
|
131
|
+
|
|
132
|
+
// ── Type guards ───────────────────────────────────────────────────────────
|
|
133
|
+
// Each guard validates `type` plus required body fields so it's safe to use
|
|
134
|
+
// on messages that have round-tripped through JSON / a relay.
|
|
135
|
+
|
|
136
|
+
const hasStr = (b: unknown, k: string): boolean =>
|
|
137
|
+
!!b && typeof (b as Record<string, unknown>)[k] === 'string';
|
|
138
|
+
const hasNum = (b: unknown, k: string): boolean =>
|
|
139
|
+
!!b && typeof (b as Record<string, unknown>)[k] === 'number';
|
|
140
|
+
const hasBool = (b: unknown, k: string): boolean =>
|
|
141
|
+
!!b && typeof (b as Record<string, unknown>)[k] === 'boolean';
|
|
142
|
+
const hasBytes = (b: unknown, k: string): boolean =>
|
|
143
|
+
!!b && (b as Record<string, unknown>)[k] instanceof Uint8Array;
|
|
144
|
+
const hasBytesArray = (b: unknown, k: string): boolean => {
|
|
145
|
+
const v = b ? (b as Record<string, unknown>)[k] : undefined;
|
|
146
|
+
return Array.isArray(v) && v.every(x => x instanceof Uint8Array);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export function isCohortAdvertMessage(m: BaseMessage): m is CohortAdvertMessage {
|
|
150
|
+
return m.type === COHORT_ADVERT
|
|
151
|
+
&& hasStr(m.body, 'cohortId')
|
|
152
|
+
&& hasNum(m.body, 'cohortSize')
|
|
153
|
+
&& hasStr(m.body, 'beaconType')
|
|
154
|
+
&& hasStr(m.body, 'network')
|
|
155
|
+
&& hasBytes(m.body, 'communicationPk');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function isCohortOptInMessage(m: BaseMessage): m is CohortOptInMessage {
|
|
159
|
+
return m.type === COHORT_OPT_IN
|
|
160
|
+
&& hasStr(m.body, 'cohortId')
|
|
161
|
+
&& hasBytes(m.body, 'participantPk')
|
|
162
|
+
&& hasBytes(m.body, 'communicationPk');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function isCohortOptInAcceptMessage(m: BaseMessage): m is CohortOptInAcceptMessage {
|
|
166
|
+
return m.type === COHORT_OPT_IN_ACCEPT && hasStr(m.body, 'cohortId');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function isCohortReadyMessage(m: BaseMessage): m is CohortReadyMessage {
|
|
170
|
+
return m.type === COHORT_READY
|
|
171
|
+
&& hasStr(m.body, 'cohortId')
|
|
172
|
+
&& hasStr(m.body, 'beaconAddress')
|
|
173
|
+
&& hasBytesArray(m.body, 'cohortKeys');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function isSubmitUpdateMessage(m: BaseMessage): m is SubmitUpdateMessage {
|
|
177
|
+
return m.type === SUBMIT_UPDATE
|
|
178
|
+
&& hasStr(m.body, 'cohortId')
|
|
179
|
+
&& !!m.body && typeof (m.body as Record<string, unknown>).signedUpdate === 'object';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function isDistributeAggregatedDataMessage(m: BaseMessage): m is DistributeAggregatedDataMessage {
|
|
183
|
+
return m.type === DISTRIBUTE_AGGREGATED_DATA
|
|
184
|
+
&& hasStr(m.body, 'cohortId')
|
|
185
|
+
&& hasStr(m.body, 'beaconType')
|
|
186
|
+
&& hasStr(m.body, 'signalBytesHex');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function isValidationAckMessage(m: BaseMessage): m is ValidationAckMessage {
|
|
190
|
+
return m.type === VALIDATION_ACK
|
|
191
|
+
&& hasStr(m.body, 'cohortId')
|
|
192
|
+
&& hasBool(m.body, 'approved');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function isAuthorizationRequestMessage(m: BaseMessage): m is AuthorizationRequestMessage {
|
|
196
|
+
return m.type === AUTHORIZATION_REQUEST
|
|
197
|
+
&& hasStr(m.body, 'cohortId')
|
|
198
|
+
&& hasStr(m.body, 'sessionId')
|
|
199
|
+
&& hasStr(m.body, 'pendingTx')
|
|
200
|
+
&& hasStr(m.body, 'prevOutScriptHex')
|
|
201
|
+
&& hasStr(m.body, 'prevOutValue');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function isNonceContributionMessage(m: BaseMessage): m is NonceContributionMessage {
|
|
205
|
+
return m.type === NONCE_CONTRIBUTION
|
|
206
|
+
&& hasStr(m.body, 'cohortId')
|
|
207
|
+
&& hasStr(m.body, 'sessionId')
|
|
208
|
+
&& hasBytes(m.body, 'nonceContribution');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function isAggregatedNonceMessage(m: BaseMessage): m is AggregatedNonceMessage {
|
|
212
|
+
return m.type === AGGREGATED_NONCE
|
|
213
|
+
&& hasStr(m.body, 'cohortId')
|
|
214
|
+
&& hasStr(m.body, 'sessionId')
|
|
215
|
+
&& hasBytes(m.body, 'aggregatedNonce');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function isSignatureAuthorizationMessage(m: BaseMessage): m is SignatureAuthorizationMessage {
|
|
219
|
+
return m.type === SIGNATURE_AUTHORIZATION
|
|
220
|
+
&& hasStr(m.body, 'cohortId')
|
|
221
|
+
&& hasStr(m.body, 'sessionId')
|
|
222
|
+
&& hasBytes(m.body, 'partialSignature');
|
|
223
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { canonicalHash
|
|
1
|
+
import { canonicalHash } from '@did-btcr2/common';
|
|
2
2
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
3
3
|
import type { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
4
4
|
import type { SerializedSMTProof} from '@did-btcr2/smt';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
6
|
+
import { Transaction } from '@scure/btc-signer';
|
|
7
|
+
import { getBeaconStrategy } from './beacon-strategy.js';
|
|
8
8
|
import { AggregationCohort } from './cohort.js';
|
|
9
9
|
import { AggregationParticipantError } from './errors.js';
|
|
10
10
|
import type { BaseMessage } from './messages/base.js';
|
|
11
|
+
import { AGGREGATION_WIRE_VERSION } from './messages/base.js';
|
|
11
12
|
import {
|
|
12
13
|
AGGREGATED_NONCE,
|
|
13
14
|
AUTHORIZATION_REQUEST,
|
|
@@ -61,6 +62,8 @@ export interface PendingSigningRequest {
|
|
|
61
62
|
cohortId: string;
|
|
62
63
|
sessionId: string;
|
|
63
64
|
pendingTxHex: string;
|
|
65
|
+
/** Hex-encoded scriptPubKey of the UTXO being spent. Required for BIP-341 sighash. */
|
|
66
|
+
prevOutScriptHex: string;
|
|
64
67
|
prevOutValue: string;
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -110,6 +113,10 @@ export class AggregationParticipant {
|
|
|
110
113
|
* outgoing messages — those come exclusively from action methods.
|
|
111
114
|
*/
|
|
112
115
|
public receive(message: BaseMessage): void {
|
|
116
|
+
// Reject messages whose wire version doesn't match what this build speaks.
|
|
117
|
+
if(message.version === undefined || message.version !== AGGREGATION_WIRE_VERSION) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
113
120
|
const type = message.type;
|
|
114
121
|
switch(type) {
|
|
115
122
|
case COHORT_ADVERT:
|
|
@@ -291,46 +298,28 @@ export class AggregationParticipant {
|
|
|
291
298
|
if(!state.submittedUpdate) return;
|
|
292
299
|
|
|
293
300
|
const beaconType = message.body?.beaconType;
|
|
301
|
+
if(!beaconType) return;
|
|
302
|
+
const strategy = getBeaconStrategy(beaconType);
|
|
303
|
+
if(!strategy) return;
|
|
304
|
+
|
|
294
305
|
const signalBytesHex = message.body?.signalBytesHex ?? '';
|
|
295
306
|
const expectedHash = canonicalHash(state.submittedUpdate);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
state.validation = {
|
|
303
|
-
cohortId,
|
|
304
|
-
beaconType,
|
|
305
|
-
signalBytesHex,
|
|
306
|
-
casAnnouncement,
|
|
307
|
-
expectedHash,
|
|
308
|
-
matches,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
} else if(beaconType === 'SMTBeacon') {
|
|
312
|
-
const smtProof = message.body?.smtProof as unknown as SerializedSMTProof | undefined;
|
|
313
|
-
if(smtProof?.updateId && smtProof?.nonce) {
|
|
314
|
-
// Verify updateId matches the canonicalized update hash
|
|
315
|
-
const canonicalBytes = new TextEncoder().encode(canonicalize(state.submittedUpdate));
|
|
316
|
-
const expectedUpdateId = hashToHex(blockHash(canonicalBytes));
|
|
317
|
-
if(smtProof.updateId === expectedUpdateId) {
|
|
318
|
-
// Verify Merkle inclusion
|
|
319
|
-
const index = didToIndex(this.did);
|
|
320
|
-
const candidateHash = blockHash(blockHash(hexToHash(smtProof.nonce)), hexToHash(smtProof.updateId));
|
|
321
|
-
matches = verifySerializedProof(smtProof, index, candidateHash);
|
|
322
|
-
}
|
|
323
|
-
state.validation = {
|
|
324
|
-
cohortId,
|
|
325
|
-
beaconType,
|
|
326
|
-
signalBytesHex,
|
|
327
|
-
smtProof,
|
|
328
|
-
expectedHash,
|
|
329
|
-
matches,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
}
|
|
307
|
+
const result = strategy.validateParticipantView({
|
|
308
|
+
participantDid : this.did,
|
|
309
|
+
submittedUpdate : state.submittedUpdate,
|
|
310
|
+
expectedHash,
|
|
311
|
+
body : message.body!,
|
|
312
|
+
});
|
|
333
313
|
|
|
314
|
+
state.validation = {
|
|
315
|
+
cohortId,
|
|
316
|
+
beaconType,
|
|
317
|
+
signalBytesHex,
|
|
318
|
+
expectedHash,
|
|
319
|
+
matches : result.matches,
|
|
320
|
+
casAnnouncement : result.casAnnouncement,
|
|
321
|
+
smtProof : result.smtProof,
|
|
322
|
+
};
|
|
334
323
|
state.phase = ParticipantCohortPhase.AwaitingValidation;
|
|
335
324
|
}
|
|
336
325
|
|
|
@@ -395,13 +384,15 @@ export class AggregationParticipant {
|
|
|
395
384
|
|
|
396
385
|
const sessionId = message.body?.sessionId;
|
|
397
386
|
const pendingTxHex = message.body?.pendingTx;
|
|
387
|
+
const prevOutScriptHex = message.body?.prevOutScriptHex;
|
|
398
388
|
const prevOutValue = message.body?.prevOutValue;
|
|
399
|
-
if(!sessionId || !pendingTxHex || !prevOutValue) return;
|
|
389
|
+
if(!sessionId || !pendingTxHex || !prevOutScriptHex || !prevOutValue) return;
|
|
400
390
|
|
|
401
391
|
state.signingRequest = {
|
|
402
392
|
cohortId,
|
|
403
393
|
sessionId,
|
|
404
394
|
pendingTxHex,
|
|
395
|
+
prevOutScriptHex,
|
|
405
396
|
prevOutValue,
|
|
406
397
|
};
|
|
407
398
|
state.phase = ParticipantCohortPhase.AwaitingSigning;
|
|
@@ -425,11 +416,14 @@ export class AggregationParticipant {
|
|
|
425
416
|
);
|
|
426
417
|
}
|
|
427
418
|
|
|
428
|
-
const tx = Transaction.
|
|
419
|
+
const tx = Transaction.fromRaw(hexToBytes(state.signingRequest.pendingTxHex));
|
|
429
420
|
|
|
430
|
-
// Derive UTXO metadata for Taproot sighash (BIP-341).
|
|
431
|
-
//
|
|
432
|
-
|
|
421
|
+
// Derive UTXO metadata for Taproot sighash (BIP-341). Use the script
|
|
422
|
+
// supplied by the service in AUTHORIZATION_REQUEST rather than reading
|
|
423
|
+
// the change output: input and change may use different scripts in future
|
|
424
|
+
// beacon designs, and the prevOutScript must be the UTXO script, not the
|
|
425
|
+
// change script.
|
|
426
|
+
const prevOutScripts = [hexToBytes(state.signingRequest.prevOutScriptHex)];
|
|
433
427
|
const prevOutValues = [BigInt(state.signingRequest.prevOutValue)];
|
|
434
428
|
|
|
435
429
|
const session = new BeaconSigningSession({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { SerializedSMTProof } from '@did-btcr2/smt';
|
|
1
2
|
import type { CohortAdvert, PendingSigningRequest, PendingValidation } from '../participant.js';
|
|
2
|
-
import type { AggregationResult, PendingOptIn } from '../service.js';
|
|
3
|
+
import type { AggregationResult, PendingOptIn, Rejection } from '../service.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* AggregationServiceRunner events are emitted by the AggregationServiceRunner to signal important
|
|
@@ -22,6 +23,13 @@ export type AggregationServiceEvents = {
|
|
|
22
23
|
/** A participant has submitted a signed update. */
|
|
23
24
|
'update-received': [{ participantDid: string }];
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* An inbound message was silently dropped by the state machine (bad proof,
|
|
28
|
+
* oversized payload, wrong wire version, etc.). Fires for *any* rejection,
|
|
29
|
+
* not just SUBMIT_UPDATE.
|
|
30
|
+
*/
|
|
31
|
+
'message-rejected': [Rejection & { cohortId: string }];
|
|
32
|
+
|
|
25
33
|
/** Aggregated data has been distributed to all participants for validation. */
|
|
26
34
|
'data-distributed': [{ cohortId: string }];
|
|
27
35
|
|
|
@@ -37,6 +45,9 @@ export type AggregationServiceEvents = {
|
|
|
37
45
|
/** Signing complete — final aggregated signature is ready to broadcast. */
|
|
38
46
|
'signing-complete': [AggregationResult];
|
|
39
47
|
|
|
48
|
+
/** Cohort transitioned to Failed phase (e.g. a participant rejected validation). */
|
|
49
|
+
'cohort-failed': [{ cohortId: string; reason: string }];
|
|
50
|
+
|
|
40
51
|
/** A non-fatal error occurred. Fatal errors reject the run() promise. */
|
|
41
52
|
'error': [Error];
|
|
42
53
|
};
|
|
@@ -66,8 +77,21 @@ export type AggregationParticipantEvents = {
|
|
|
66
77
|
/** Signing request has arrived. Fires before the sign approval callback. */
|
|
67
78
|
'signing-requested': [PendingSigningRequest];
|
|
68
79
|
|
|
69
|
-
/**
|
|
70
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Cohort signing is complete from this participant's perspective.
|
|
82
|
+
* Includes the aggregated sidecar data the participant needs to keep for
|
|
83
|
+
* future DID resolution: the CAS Announcement map (for CAS beacons) or the
|
|
84
|
+
* SMT inclusion proof (for SMT beacons).
|
|
85
|
+
*/
|
|
86
|
+
'cohort-complete': [{
|
|
87
|
+
cohortId: string;
|
|
88
|
+
beaconAddress: string;
|
|
89
|
+
beaconType: string;
|
|
90
|
+
/** DID → base64url update hash. Populated only for CAS beacons. */
|
|
91
|
+
casAnnouncement?: Record<string, string>;
|
|
92
|
+
/** Merkle inclusion proof for this participant's slot. Populated only for SMT beacons. */
|
|
93
|
+
smtProof?: SerializedSMTProof;
|
|
94
|
+
}];
|
|
71
95
|
|
|
72
96
|
/** Cohort failed (rejected validation, signing error, etc.). */
|
|
73
97
|
'cohort-failed': [{ cohortId: string; reason: string }];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
2
2
|
import type { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
3
|
+
import type { SerializedSMTProof } from '@did-btcr2/smt';
|
|
3
4
|
import type { BaseMessage } from '../messages/base.js';
|
|
4
5
|
import {
|
|
5
6
|
AGGREGATED_NONCE,
|
|
@@ -87,11 +88,7 @@ export interface AggregationParticipantRunnerOptions {
|
|
|
87
88
|
* keys: myKeys,
|
|
88
89
|
* shouldJoin: async (advert) => advert.beaconType === 'CASBeacon',
|
|
89
90
|
* onProvideUpdate: async ({ beaconAddress }) => {
|
|
90
|
-
<<<<<<< Updated upstream
|
|
91
|
-
* return Update.sign(myDid, unsigned, vm, secretKey);
|
|
92
|
-
=======
|
|
93
91
|
* return Updater.sign(myDid, unsigned, vm, secretKey);
|
|
94
|
-
>>>>>>> Stashed changes
|
|
95
92
|
* },
|
|
96
93
|
* });
|
|
97
94
|
*
|
|
@@ -143,9 +140,29 @@ export class AggregationParticipantRunner extends TypedEventEmitter<AggregationP
|
|
|
143
140
|
this.#registerHandlers();
|
|
144
141
|
}
|
|
145
142
|
|
|
146
|
-
/** Stop the runner
|
|
143
|
+
/** Stop the runner and detach transport handlers. Safe to call repeatedly. */
|
|
147
144
|
stop(): void {
|
|
148
145
|
this.#stopped = true;
|
|
146
|
+
this.#unregisterHandlers();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Message types this runner listens for on the transport. */
|
|
150
|
+
static readonly #HANDLED_MESSAGE_TYPES: readonly string[] = [
|
|
151
|
+
COHORT_ADVERT,
|
|
152
|
+
COHORT_OPT_IN_ACCEPT,
|
|
153
|
+
COHORT_READY,
|
|
154
|
+
DISTRIBUTE_AGGREGATED_DATA,
|
|
155
|
+
AUTHORIZATION_REQUEST,
|
|
156
|
+
AGGREGATED_NONCE,
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
160
|
+
#unregisterHandlers(): void {
|
|
161
|
+
if(!this.#handlersRegistered) return;
|
|
162
|
+
this.#handlersRegistered = false;
|
|
163
|
+
for(const type of AggregationParticipantRunner.#HANDLED_MESSAGE_TYPES) {
|
|
164
|
+
this.#transport.unregisterMessageHandler(this.#did, type);
|
|
165
|
+
}
|
|
149
166
|
}
|
|
150
167
|
|
|
151
168
|
/**
|
|
@@ -154,7 +171,15 @@ export class AggregationParticipantRunner extends TypedEventEmitter<AggregationP
|
|
|
154
171
|
*/
|
|
155
172
|
static async joinFirst(
|
|
156
173
|
options: AggregationParticipantRunnerOptions
|
|
157
|
-
): Promise<{
|
|
174
|
+
): Promise<{
|
|
175
|
+
cohortId: string;
|
|
176
|
+
beaconAddress: string;
|
|
177
|
+
beaconType: string;
|
|
178
|
+
/** DID → base64url update hash. Populated only for CAS beacons. */
|
|
179
|
+
casAnnouncement?: Record<string, string>;
|
|
180
|
+
/** Merkle inclusion proof for this participant's slot. Populated only for SMT beacons. */
|
|
181
|
+
smtProof?: SerializedSMTProof;
|
|
182
|
+
}> {
|
|
158
183
|
return new Promise((resolve, reject) => {
|
|
159
184
|
const runner = new AggregationParticipantRunner(options);
|
|
160
185
|
runner.once('cohort-complete', (info) => {
|
|
@@ -341,7 +366,16 @@ export class AggregationParticipantRunner extends TypedEventEmitter<AggregationP
|
|
|
341
366
|
if (this.session.getCohortPhase(cohortId) === ParticipantCohortPhase.Complete) {
|
|
342
367
|
const info = this.session.joinedCohorts.get(cohortId);
|
|
343
368
|
if (info) {
|
|
344
|
-
|
|
369
|
+
// Surface the sidecar data the participant will need for future resolutions:
|
|
370
|
+
// the CAS Announcement map (CAS beacons) or their SMT inclusion proof.
|
|
371
|
+
const validation = this.session.pendingValidations.get(cohortId);
|
|
372
|
+
this.emit('cohort-complete', {
|
|
373
|
+
cohortId,
|
|
374
|
+
beaconAddress : info.beaconAddress,
|
|
375
|
+
beaconType : validation?.beaconType ?? '',
|
|
376
|
+
casAnnouncement : validation?.casAnnouncement,
|
|
377
|
+
smtProof : validation?.smtProof,
|
|
378
|
+
});
|
|
345
379
|
}
|
|
346
380
|
}
|
|
347
381
|
} catch (err) {
|