@oxy.so/protocol 1.6.0 → 1.7.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/cjs/platform/crypto.native.js +40 -3
- package/dist/esm/platform/crypto.native.js +40 -3
- package/dist/types/platform/expoTypes.d.ts +19 -8
- package/package.json +13 -13
- package/src/__tests__/commonsIdentityBridge.test.ts +79 -0
- package/src/__tests__/nodeApp.test.ts +5 -8
- package/src/node/nodeApp.ts +1 -1
- package/src/platform/crypto.native.ts +66 -8
- package/src/platform/expoTypes.ts +23 -8
|
@@ -151,6 +151,20 @@ function narrowDescription(value) {
|
|
|
151
151
|
return null;
|
|
152
152
|
return { v, publicKey };
|
|
153
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* The organization answer must name the organization asked for. A Commons that
|
|
156
|
+
* predates organization keys ignores the `accountId` extra and answers for the
|
|
157
|
+
* PERSONAL identity, without an echo; that answer is refused here, because a
|
|
158
|
+
* personal key taken for an organization's is money sent to the wrong person.
|
|
159
|
+
*/
|
|
160
|
+
function echoes(value, accountId) {
|
|
161
|
+
const r = record(value);
|
|
162
|
+
return r && r.accountId === accountId ? r : null;
|
|
163
|
+
}
|
|
164
|
+
function narrowOrganizationDescription(value, accountId) {
|
|
165
|
+
const description = narrowDescription(echoes(value, accountId));
|
|
166
|
+
return description ? { ...description, accountId } : null;
|
|
167
|
+
}
|
|
154
168
|
function narrowProof(value) {
|
|
155
169
|
const r = record(value);
|
|
156
170
|
const publicKey = hexField(r?.publicKey, 130);
|
|
@@ -191,10 +205,33 @@ function loadCommonsIdentityBridge() {
|
|
|
191
205
|
}
|
|
192
206
|
const m = native;
|
|
193
207
|
return {
|
|
194
|
-
describe: () =>
|
|
208
|
+
describe: (accountId) => {
|
|
209
|
+
if (accountId === undefined)
|
|
210
|
+
return ask(() => m.describe(), narrowDescription);
|
|
211
|
+
const describeOrganization = m.describeOrganization;
|
|
212
|
+
if (typeof describeOrganization !== 'function')
|
|
213
|
+
return Promise.resolve(null);
|
|
214
|
+
return ask(() => describeOrganization.call(m, accountId), (v) => narrowOrganizationDescription(v, accountId));
|
|
215
|
+
},
|
|
195
216
|
proveIdentity: (challenge) => ask(() => m.proveIdentity(challenge), narrowProof),
|
|
196
|
-
deriveScopedSeed: (info
|
|
197
|
-
|
|
217
|
+
deriveScopedSeed: (info, accountId) => {
|
|
218
|
+
if (accountId === undefined) {
|
|
219
|
+
return ask(() => m.deriveScopedSeed(info), (v) => hexField(v, 64));
|
|
220
|
+
}
|
|
221
|
+
const derive = m.deriveOrganizationScopedSeed;
|
|
222
|
+
if (typeof derive !== 'function')
|
|
223
|
+
return Promise.resolve(null);
|
|
224
|
+
return ask(() => derive.call(m, info, accountId), (v) => hexField(echoes(v, accountId)?.seed, 64));
|
|
225
|
+
},
|
|
226
|
+
signSocialReceive: (index, digest, accountId) => {
|
|
227
|
+
if (accountId === undefined) {
|
|
228
|
+
return ask(() => m.signSocialReceive(index, digest), narrowSocialReceive);
|
|
229
|
+
}
|
|
230
|
+
const sign = m.signOrganizationSocialReceive;
|
|
231
|
+
if (typeof sign !== 'function')
|
|
232
|
+
return Promise.resolve(null);
|
|
233
|
+
return ask(() => sign.call(m, index, digest, accountId), (v) => narrowSocialReceive(echoes(v, accountId)));
|
|
234
|
+
},
|
|
198
235
|
};
|
|
199
236
|
});
|
|
200
237
|
}
|
|
@@ -142,6 +142,20 @@ function narrowDescription(value) {
|
|
|
142
142
|
return null;
|
|
143
143
|
return { v, publicKey };
|
|
144
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* The organization answer must name the organization asked for. A Commons that
|
|
147
|
+
* predates organization keys ignores the `accountId` extra and answers for the
|
|
148
|
+
* PERSONAL identity, without an echo; that answer is refused here, because a
|
|
149
|
+
* personal key taken for an organization's is money sent to the wrong person.
|
|
150
|
+
*/
|
|
151
|
+
function echoes(value, accountId) {
|
|
152
|
+
const r = record(value);
|
|
153
|
+
return r && r.accountId === accountId ? r : null;
|
|
154
|
+
}
|
|
155
|
+
function narrowOrganizationDescription(value, accountId) {
|
|
156
|
+
const description = narrowDescription(echoes(value, accountId));
|
|
157
|
+
return description ? { ...description, accountId } : null;
|
|
158
|
+
}
|
|
145
159
|
function narrowProof(value) {
|
|
146
160
|
const r = record(value);
|
|
147
161
|
const publicKey = hexField(r?.publicKey, 130);
|
|
@@ -182,10 +196,33 @@ export function loadCommonsIdentityBridge() {
|
|
|
182
196
|
}
|
|
183
197
|
const m = native;
|
|
184
198
|
return {
|
|
185
|
-
describe: () =>
|
|
199
|
+
describe: (accountId) => {
|
|
200
|
+
if (accountId === undefined)
|
|
201
|
+
return ask(() => m.describe(), narrowDescription);
|
|
202
|
+
const describeOrganization = m.describeOrganization;
|
|
203
|
+
if (typeof describeOrganization !== 'function')
|
|
204
|
+
return Promise.resolve(null);
|
|
205
|
+
return ask(() => describeOrganization.call(m, accountId), (v) => narrowOrganizationDescription(v, accountId));
|
|
206
|
+
},
|
|
186
207
|
proveIdentity: (challenge) => ask(() => m.proveIdentity(challenge), narrowProof),
|
|
187
|
-
deriveScopedSeed: (info
|
|
188
|
-
|
|
208
|
+
deriveScopedSeed: (info, accountId) => {
|
|
209
|
+
if (accountId === undefined) {
|
|
210
|
+
return ask(() => m.deriveScopedSeed(info), (v) => hexField(v, 64));
|
|
211
|
+
}
|
|
212
|
+
const derive = m.deriveOrganizationScopedSeed;
|
|
213
|
+
if (typeof derive !== 'function')
|
|
214
|
+
return Promise.resolve(null);
|
|
215
|
+
return ask(() => derive.call(m, info, accountId), (v) => hexField(echoes(v, accountId)?.seed, 64));
|
|
216
|
+
},
|
|
217
|
+
signSocialReceive: (index, digest, accountId) => {
|
|
218
|
+
if (accountId === undefined) {
|
|
219
|
+
return ask(() => m.signSocialReceive(index, digest), narrowSocialReceive);
|
|
220
|
+
}
|
|
221
|
+
const sign = m.signOrganizationSocialReceive;
|
|
222
|
+
if (typeof sign !== 'function')
|
|
223
|
+
return Promise.resolve(null);
|
|
224
|
+
return ask(() => sign.call(m, index, digest, accountId), (v) => narrowSocialReceive(echoes(v, accountId)));
|
|
225
|
+
},
|
|
189
226
|
};
|
|
190
227
|
});
|
|
191
228
|
}
|
|
@@ -79,6 +79,12 @@ export interface CommonsIdentityDescription {
|
|
|
79
79
|
v: number;
|
|
80
80
|
/** The identity's public key, lowercase uncompressed SEC1 hex. */
|
|
81
81
|
publicKey: string;
|
|
82
|
+
/**
|
|
83
|
+
* Set only on an answer for an ORGANIZATION key (ADR 0061 D6): the account id
|
|
84
|
+
* Commons echoed, always equal to the one asked for. An answer without it is
|
|
85
|
+
* the personal identity and is never taken as an organization's.
|
|
86
|
+
*/
|
|
87
|
+
accountId?: string;
|
|
82
88
|
}
|
|
83
89
|
/**
|
|
84
90
|
* A signed server challenge from Commons (`proveIdentity`). The fields plug
|
|
@@ -113,19 +119,24 @@ export interface CommonsSocialReceiveSignature {
|
|
|
113
119
|
* path in `@oxy.so/core`'s `KeyManager` owns iOS sharing.
|
|
114
120
|
*/
|
|
115
121
|
export interface CommonsIdentityBridge {
|
|
116
|
-
/**
|
|
117
|
-
|
|
122
|
+
/**
|
|
123
|
+
* The public key of the identity Commons holds, or, with `accountId`, of that
|
|
124
|
+
* organization's key (ADR 0061 D6).
|
|
125
|
+
*/
|
|
126
|
+
describe(accountId?: string): Promise<CommonsIdentityDescription | null>;
|
|
118
127
|
/** Sign a server challenge (64 lowercase hex) for `POST /auth/verify`. */
|
|
119
128
|
proveIdentity(challenge: string): Promise<CommonsIdentityProof | null>;
|
|
120
129
|
/**
|
|
121
|
-
* HKDF-SHA256 over the identity key,
|
|
122
|
-
*
|
|
123
|
-
* Commons allows each caller package only
|
|
130
|
+
* HKDF-SHA256 over the identity key (or, with `accountId`, the organization's
|
|
131
|
+
* key), 32 bytes as lowercase hex, identical to `KeyManager.deriveScopedSeed`
|
|
132
|
+
* on the device that holds the key. Commons allows each caller package only
|
|
133
|
+
* its own `info` labels.
|
|
124
134
|
*/
|
|
125
|
-
deriveScopedSeed(info: string): Promise<string | null>;
|
|
135
|
+
deriveScopedSeed(info: string, accountId?: string): Promise<string | null>;
|
|
126
136
|
/**
|
|
127
137
|
* Sign a 32-byte digest (64 hex) with the social-receive child key `index`
|
|
128
|
-
* of the identity
|
|
138
|
+
* of the identity, or with `accountId`, of that organization's key. Allowed
|
|
139
|
+
* for the wallet app only.
|
|
129
140
|
*/
|
|
130
|
-
signSocialReceive(index: number, digest: string): Promise<CommonsSocialReceiveSignature | null>;
|
|
141
|
+
signSocialReceive(index: number, digest: string, accountId?: string): Promise<CommonsSocialReceiveSignature | null>;
|
|
131
142
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/protocol",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Oxy Protocol — the app-agnostic base substrate: signed-record envelope, canonical JSON, signature/verification, and platform crypto. Reused by any Oxy app to decentralize its own content.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
},
|
|
134
134
|
"dependencies": {
|
|
135
135
|
"@noble/curves": "^2.4.0",
|
|
136
|
-
"@oxy.so/contracts": "^6.
|
|
136
|
+
"@oxy.so/contracts": "^6.11.0"
|
|
137
137
|
},
|
|
138
138
|
"peerDependencies": {
|
|
139
139
|
"@react-native-async-storage/async-storage": "<4.0.0",
|
|
@@ -158,16 +158,16 @@
|
|
|
158
158
|
},
|
|
159
159
|
"devDependencies": {
|
|
160
160
|
"@react-native-async-storage/async-storage": "^3.1.1",
|
|
161
|
-
"@types/express": "^
|
|
162
|
-
"@types/node": "^
|
|
163
|
-
"@types/supertest": "^
|
|
164
|
-
"expo-crypto": "~
|
|
165
|
-
"expo-modules-core": "~58.0.
|
|
166
|
-
"expo-secure-store": "~
|
|
167
|
-
"express": "^
|
|
168
|
-
"jest": "^30.5.
|
|
169
|
-
"supertest": "^7.
|
|
170
|
-
"ts-jest": "^29.4.
|
|
171
|
-
"typescript": "
|
|
161
|
+
"@types/express": "^5.0.6",
|
|
162
|
+
"@types/node": "^26.6.6",
|
|
163
|
+
"@types/supertest": "^7.2.1",
|
|
164
|
+
"expo-crypto": "~58.0.7",
|
|
165
|
+
"expo-modules-core": "~58.0.30",
|
|
166
|
+
"expo-secure-store": "~58.0.2",
|
|
167
|
+
"express": "^5.3.0",
|
|
168
|
+
"jest": "^30.5.2",
|
|
169
|
+
"supertest": "^7.3.1",
|
|
170
|
+
"ts-jest": "^29.4.15",
|
|
171
|
+
"typescript": "~6.0.3"
|
|
172
172
|
}
|
|
173
173
|
}
|
|
@@ -87,6 +87,85 @@ describe('loadCommonsIdentityBridge (native)', () => {
|
|
|
87
87
|
await expect(bridge?.signSocialReceive(0, '11'.repeat(32))).resolves.toBeNull();
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
test('organization requests go to the organization methods and must echo the account', async () => {
|
|
91
|
+
const ORG = '0123456789abcdef01234567';
|
|
92
|
+
const calls: unknown[][] = [];
|
|
93
|
+
mockNative({
|
|
94
|
+
describe: async () => ({ v: 2, publicKey: PUBLIC_KEY }),
|
|
95
|
+
proveIdentity: async () => null,
|
|
96
|
+
deriveScopedSeed: async () => 'cd'.repeat(32),
|
|
97
|
+
signSocialReceive: async () => ({ signature: '3045bb', publicKey: `02${'ef'.repeat(32)}` }),
|
|
98
|
+
describeOrganization: async (accountId: string) => {
|
|
99
|
+
calls.push(['describeOrganization', accountId]);
|
|
100
|
+
return { v: 3, publicKey: PUBLIC_KEY, accountId };
|
|
101
|
+
},
|
|
102
|
+
deriveOrganizationScopedSeed: async (info: string, accountId: string) => {
|
|
103
|
+
calls.push(['deriveOrganizationScopedSeed', info, accountId]);
|
|
104
|
+
return { seed: 'ee'.repeat(32), accountId };
|
|
105
|
+
},
|
|
106
|
+
signOrganizationSocialReceive: async (index: number, digest: string, accountId: string) => {
|
|
107
|
+
calls.push(['signOrganizationSocialReceive', index, digest, accountId]);
|
|
108
|
+
return { signature: '3046cc', publicKey: `03${'aa'.repeat(32)}`, accountId };
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const bridge = await loadBridge();
|
|
112
|
+
await expect(bridge?.describe(ORG)).resolves.toEqual({
|
|
113
|
+
v: 3,
|
|
114
|
+
publicKey: PUBLIC_KEY,
|
|
115
|
+
accountId: ORG,
|
|
116
|
+
});
|
|
117
|
+
await expect(bridge?.deriveScopedSeed('peable/faircoin/v1', ORG)).resolves.toBe(
|
|
118
|
+
'ee'.repeat(32),
|
|
119
|
+
);
|
|
120
|
+
await expect(bridge?.signSocialReceive(4, '11'.repeat(32), ORG)).resolves.toEqual({
|
|
121
|
+
signature: '3046cc',
|
|
122
|
+
publicKey: `03${'aa'.repeat(32)}`,
|
|
123
|
+
});
|
|
124
|
+
// The personal path is untouched.
|
|
125
|
+
await expect(bridge?.describe()).resolves.toEqual({ v: 2, publicKey: PUBLIC_KEY });
|
|
126
|
+
expect(calls).toEqual([
|
|
127
|
+
['describeOrganization', ORG],
|
|
128
|
+
['deriveOrganizationScopedSeed', 'peable/faircoin/v1', ORG],
|
|
129
|
+
['signOrganizationSocialReceive', 4, '11'.repeat(32), ORG],
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('an organization answer without the echo, or for another account, is null', async () => {
|
|
134
|
+
const ORG = '0123456789abcdef01234567';
|
|
135
|
+
mockNative({
|
|
136
|
+
describe: async () => ({ v: 2, publicKey: PUBLIC_KEY }),
|
|
137
|
+
proveIdentity: async () => null,
|
|
138
|
+
deriveScopedSeed: async () => 'cd'.repeat(32),
|
|
139
|
+
signSocialReceive: async () => null,
|
|
140
|
+
// An old Commons ignored the extra and answered for the personal key.
|
|
141
|
+
describeOrganization: async () => ({ v: 2, publicKey: PUBLIC_KEY }),
|
|
142
|
+
deriveOrganizationScopedSeed: async () => ({ seed: 'ee'.repeat(32), accountId: 'other' }),
|
|
143
|
+
signOrganizationSocialReceive: async () => ({
|
|
144
|
+
signature: '3046cc',
|
|
145
|
+
publicKey: `03${'aa'.repeat(32)}`,
|
|
146
|
+
}),
|
|
147
|
+
});
|
|
148
|
+
const bridge = await loadBridge();
|
|
149
|
+
await expect(bridge?.describe(ORG)).resolves.toBeNull();
|
|
150
|
+
await expect(bridge?.deriveScopedSeed('peable/faircoin/v1', ORG)).resolves.toBeNull();
|
|
151
|
+
await expect(bridge?.signSocialReceive(0, '11'.repeat(32), ORG)).resolves.toBeNull();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('a native module without the organization methods answers no organization', async () => {
|
|
155
|
+
mockNative({
|
|
156
|
+
describe: async () => ({ v: 2, publicKey: PUBLIC_KEY }),
|
|
157
|
+
proveIdentity: async () => null,
|
|
158
|
+
deriveScopedSeed: async () => 'cd'.repeat(32),
|
|
159
|
+
signSocialReceive: async () => ({ signature: '3045bb', publicKey: `02${'ef'.repeat(32)}` }),
|
|
160
|
+
});
|
|
161
|
+
const bridge = await loadBridge();
|
|
162
|
+
await expect(bridge?.describe('0123456789abcdef01234567')).resolves.toBeNull();
|
|
163
|
+
await expect(bridge?.deriveScopedSeed('x', '0123456789abcdef01234567')).resolves.toBeNull();
|
|
164
|
+
await expect(
|
|
165
|
+
bridge?.signSocialReceive(0, '11'.repeat(32), '0123456789abcdef01234567'),
|
|
166
|
+
).resolves.toBeNull();
|
|
167
|
+
});
|
|
168
|
+
|
|
90
169
|
test('the web / Node variant never has a bridge', async () => {
|
|
91
170
|
const mod = await import('../platform/crypto');
|
|
92
171
|
await expect(mod.loadCommonsIdentityBridge()).resolves.toBeNull();
|
|
@@ -432,11 +432,10 @@ describe('createNodeApp', () => {
|
|
|
432
432
|
prev = id;
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
-
// `?since
|
|
436
|
-
// take the FIRST value (a tampered
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
.query({ 'since[]': [ids[0], ids[2]] });
|
|
435
|
+
// A repeated `?since=<id1>&since=<id2>` arrives as a string[] under every
|
|
436
|
+
// Express query parser — the handler must take the FIRST value (a tampered
|
|
437
|
+
// array can never cause type confusion).
|
|
438
|
+
const res = await request(app).get('/oxy/log').query(`since=${ids[0]}&since=${ids[2]}`);
|
|
440
439
|
expect(res.status).toBe(200);
|
|
441
440
|
// Cursor resolves off ids[0] → records strictly after seq 0 → [1, 2].
|
|
442
441
|
expect(res.body.records.map((entry: { seq: number }) => entry.seq)).toEqual([1, 2]);
|
|
@@ -456,9 +455,7 @@ describe('createNodeApp', () => {
|
|
|
456
455
|
prev = await computeRecordId(envelope);
|
|
457
456
|
}
|
|
458
457
|
|
|
459
|
-
const res = await request(app)
|
|
460
|
-
.get('/oxy/log')
|
|
461
|
-
.query({ 'limit[]': ['2', '99'] });
|
|
458
|
+
const res = await request(app).get('/oxy/log').query('limit=2&limit=99');
|
|
462
459
|
expect(res.status).toBe(200);
|
|
463
460
|
expect(res.body.records.map((entry: { seq: number }) => entry.seq)).toEqual([0, 1]);
|
|
464
461
|
});
|
package/src/node/nodeApp.ts
CHANGED
|
@@ -399,7 +399,7 @@ export function createNodeApp(deps: NodeAppDependencies): NodeApp {
|
|
|
399
399
|
);
|
|
400
400
|
|
|
401
401
|
// ── Serve a content-addressed blob ───────────────────────────────────────────
|
|
402
|
-
app.get(`${NODE_BLOBS_PATH}/:hash`, async (req
|
|
402
|
+
app.get(`${NODE_BLOBS_PATH}/:hash`, async (req, res, next) => {
|
|
403
403
|
try {
|
|
404
404
|
const hash = req.params.hash.toLowerCase();
|
|
405
405
|
if (!SHA256_HEX.test(hash)) {
|
|
@@ -204,6 +204,19 @@ interface OxyIdentityNativeModule {
|
|
|
204
204
|
proveIdentity(challenge: string): Promise<unknown>;
|
|
205
205
|
deriveScopedSeed(info: string): Promise<unknown>;
|
|
206
206
|
signSocialReceive(index: number, digest: string): Promise<unknown>;
|
|
207
|
+
/**
|
|
208
|
+
* Organization keys (ADR 0061 D6), added in `@oxy.so/services` after the four
|
|
209
|
+
* above: an older native module lacks them, and every organization request
|
|
210
|
+
* then answers `null`, never the personal identity. Each answer echoes the
|
|
211
|
+
* `accountId` Commons answered for.
|
|
212
|
+
*/
|
|
213
|
+
describeOrganization?(accountId: string): Promise<unknown>;
|
|
214
|
+
deriveOrganizationScopedSeed?(info: string, accountId: string): Promise<unknown>;
|
|
215
|
+
signOrganizationSocialReceive?(
|
|
216
|
+
index: number,
|
|
217
|
+
digest: string,
|
|
218
|
+
accountId: string,
|
|
219
|
+
): Promise<unknown>;
|
|
207
220
|
}
|
|
208
221
|
|
|
209
222
|
const LOWER_HEX = /^[0-9a-f]+$/;
|
|
@@ -226,6 +239,25 @@ function narrowDescription(value: unknown): CommonsIdentityDescription | null {
|
|
|
226
239
|
return { v, publicKey };
|
|
227
240
|
}
|
|
228
241
|
|
|
242
|
+
/**
|
|
243
|
+
* The organization answer must name the organization asked for. A Commons that
|
|
244
|
+
* predates organization keys ignores the `accountId` extra and answers for the
|
|
245
|
+
* PERSONAL identity, without an echo; that answer is refused here, because a
|
|
246
|
+
* personal key taken for an organization's is money sent to the wrong person.
|
|
247
|
+
*/
|
|
248
|
+
function echoes(value: unknown, accountId: string): Record<string, unknown> | null {
|
|
249
|
+
const r = record(value);
|
|
250
|
+
return r && r.accountId === accountId ? r : null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function narrowOrganizationDescription(
|
|
254
|
+
value: unknown,
|
|
255
|
+
accountId: string,
|
|
256
|
+
): CommonsIdentityDescription | null {
|
|
257
|
+
const description = narrowDescription(echoes(value, accountId));
|
|
258
|
+
return description ? { ...description, accountId } : null;
|
|
259
|
+
}
|
|
260
|
+
|
|
229
261
|
function narrowProof(value: unknown): CommonsIdentityProof | null {
|
|
230
262
|
const r = record(value);
|
|
231
263
|
const publicKey = hexField(r?.publicKey, 130);
|
|
@@ -272,15 +304,41 @@ export function loadCommonsIdentityBridge(): Promise<CommonsIdentityBridge | nul
|
|
|
272
304
|
}
|
|
273
305
|
const m = native as OxyIdentityNativeModule;
|
|
274
306
|
return {
|
|
275
|
-
describe: (
|
|
307
|
+
describe: (accountId?: string) => {
|
|
308
|
+
if (accountId === undefined) return ask(() => m.describe(), narrowDescription);
|
|
309
|
+
const describeOrganization = m.describeOrganization;
|
|
310
|
+
if (typeof describeOrganization !== 'function') return Promise.resolve(null);
|
|
311
|
+
return ask(
|
|
312
|
+
() => describeOrganization.call(m, accountId),
|
|
313
|
+
(v) => narrowOrganizationDescription(v, accountId),
|
|
314
|
+
);
|
|
315
|
+
},
|
|
276
316
|
proveIdentity: (challenge: string) => ask(() => m.proveIdentity(challenge), narrowProof),
|
|
277
|
-
deriveScopedSeed: (info: string) =>
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
317
|
+
deriveScopedSeed: (info: string, accountId?: string) => {
|
|
318
|
+
if (accountId === undefined) {
|
|
319
|
+
return ask(
|
|
320
|
+
() => m.deriveScopedSeed(info),
|
|
321
|
+
(v) => hexField(v, 64),
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
const derive = m.deriveOrganizationScopedSeed;
|
|
325
|
+
if (typeof derive !== 'function') return Promise.resolve(null);
|
|
326
|
+
return ask(
|
|
327
|
+
() => derive.call(m, info, accountId),
|
|
328
|
+
(v) => hexField(echoes(v, accountId)?.seed, 64),
|
|
329
|
+
);
|
|
330
|
+
},
|
|
331
|
+
signSocialReceive: (index: number, digest: string, accountId?: string) => {
|
|
332
|
+
if (accountId === undefined) {
|
|
333
|
+
return ask(() => m.signSocialReceive(index, digest), narrowSocialReceive);
|
|
334
|
+
}
|
|
335
|
+
const sign = m.signOrganizationSocialReceive;
|
|
336
|
+
if (typeof sign !== 'function') return Promise.resolve(null);
|
|
337
|
+
return ask(
|
|
338
|
+
() => sign.call(m, index, digest, accountId),
|
|
339
|
+
(v) => narrowSocialReceive(echoes(v, accountId)),
|
|
340
|
+
);
|
|
341
|
+
},
|
|
284
342
|
} satisfies CommonsIdentityBridge;
|
|
285
343
|
});
|
|
286
344
|
}
|
|
@@ -82,6 +82,12 @@ export interface CommonsIdentityDescription {
|
|
|
82
82
|
v: number;
|
|
83
83
|
/** The identity's public key, lowercase uncompressed SEC1 hex. */
|
|
84
84
|
publicKey: string;
|
|
85
|
+
/**
|
|
86
|
+
* Set only on an answer for an ORGANIZATION key (ADR 0061 D6): the account id
|
|
87
|
+
* Commons echoed, always equal to the one asked for. An answer without it is
|
|
88
|
+
* the personal identity and is never taken as an organization's.
|
|
89
|
+
*/
|
|
90
|
+
accountId?: string;
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
/**
|
|
@@ -119,19 +125,28 @@ export interface CommonsSocialReceiveSignature {
|
|
|
119
125
|
* path in `@oxy.so/core`'s `KeyManager` owns iOS sharing.
|
|
120
126
|
*/
|
|
121
127
|
export interface CommonsIdentityBridge {
|
|
122
|
-
/**
|
|
123
|
-
|
|
128
|
+
/**
|
|
129
|
+
* The public key of the identity Commons holds, or, with `accountId`, of that
|
|
130
|
+
* organization's key (ADR 0061 D6).
|
|
131
|
+
*/
|
|
132
|
+
describe(accountId?: string): Promise<CommonsIdentityDescription | null>;
|
|
124
133
|
/** Sign a server challenge (64 lowercase hex) for `POST /auth/verify`. */
|
|
125
134
|
proveIdentity(challenge: string): Promise<CommonsIdentityProof | null>;
|
|
126
135
|
/**
|
|
127
|
-
* HKDF-SHA256 over the identity key,
|
|
128
|
-
*
|
|
129
|
-
* Commons allows each caller package only
|
|
136
|
+
* HKDF-SHA256 over the identity key (or, with `accountId`, the organization's
|
|
137
|
+
* key), 32 bytes as lowercase hex, identical to `KeyManager.deriveScopedSeed`
|
|
138
|
+
* on the device that holds the key. Commons allows each caller package only
|
|
139
|
+
* its own `info` labels.
|
|
130
140
|
*/
|
|
131
|
-
deriveScopedSeed(info: string): Promise<string | null>;
|
|
141
|
+
deriveScopedSeed(info: string, accountId?: string): Promise<string | null>;
|
|
132
142
|
/**
|
|
133
143
|
* Sign a 32-byte digest (64 hex) with the social-receive child key `index`
|
|
134
|
-
* of the identity
|
|
144
|
+
* of the identity, or with `accountId`, of that organization's key. Allowed
|
|
145
|
+
* for the wallet app only.
|
|
135
146
|
*/
|
|
136
|
-
signSocialReceive(
|
|
147
|
+
signSocialReceive(
|
|
148
|
+
index: number,
|
|
149
|
+
digest: string,
|
|
150
|
+
accountId?: string,
|
|
151
|
+
): Promise<CommonsSocialReceiveSignature | null>;
|
|
137
152
|
}
|