@dxos/keyring 0.8.4-main.fffef41 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/neutral/index.mjs +288 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/keyring.d.ts +10 -1
- package/dist/types/src/keyring.d.ts.map +1 -1
- package/dist/types/src/sqlite-keyring.d.ts +31 -0
- package/dist/types/src/sqlite-keyring.d.ts.map +1 -0
- package/dist/types/src/sqlite-keyring.test.d.ts +2 -0
- package/dist/types/src/sqlite-keyring.test.d.ts.map +1 -0
- package/dist/types/src/testing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -18
- package/src/index.ts +1 -0
- package/src/keyring.ts +11 -1
- package/src/sqlite-keyring.test.ts +66 -0
- package/src/sqlite-keyring.ts +159 -0
- package/dist/lib/browser/index.mjs +0 -195
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs +0 -195
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
package/package.json
CHANGED
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/keyring",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Halo Keyring.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
-
"
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
8
12
|
"author": "info@dxos.org",
|
|
9
|
-
"sideEffects":
|
|
13
|
+
"sideEffects": false,
|
|
10
14
|
"type": "module",
|
|
11
15
|
"exports": {
|
|
12
16
|
".": {
|
|
13
17
|
"source": "./src/index.ts",
|
|
14
18
|
"types": "./dist/types/src/index.d.ts",
|
|
15
|
-
"
|
|
16
|
-
"node": "./dist/lib/node-esm/index.mjs"
|
|
19
|
+
"default": "./dist/lib/neutral/index.mjs"
|
|
17
20
|
}
|
|
18
21
|
},
|
|
19
22
|
"types": "dist/types/src/index.d.ts",
|
|
20
|
-
"typesVersions": {
|
|
21
|
-
"*": {}
|
|
22
|
-
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"
|
|
30
|
-
"@dxos/
|
|
31
|
-
"@dxos/
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/util": "0.
|
|
28
|
+
"@effect/sql": "0.51.1",
|
|
29
|
+
"effect": "3.21.3",
|
|
30
|
+
"@dxos/async": "0.9.0",
|
|
31
|
+
"@dxos/crypto": "0.9.0",
|
|
32
|
+
"@dxos/codec-protobuf": "0.9.0",
|
|
33
|
+
"@dxos/debug": "0.9.0",
|
|
34
|
+
"@dxos/effect": "0.9.0",
|
|
35
|
+
"@dxos/invariant": "0.9.0",
|
|
36
|
+
"@dxos/keys": "0.9.0",
|
|
37
|
+
"@dxos/util": "0.9.0",
|
|
38
|
+
"@dxos/node-std": "0.9.0",
|
|
39
|
+
"@dxos/random-access-storage": "0.9.0",
|
|
40
|
+
"@dxos/protocols": "0.9.0",
|
|
41
|
+
"@dxos/sql-sqlite": "0.9.0"
|
|
38
42
|
},
|
|
39
43
|
"devDependencies": {},
|
|
40
44
|
"publishConfig": {
|
package/src/index.ts
CHANGED
package/src/keyring.ts
CHANGED
|
@@ -15,10 +15,20 @@ import { ComplexMap, arrayToBuffer } from '@dxos/util';
|
|
|
15
15
|
|
|
16
16
|
const KeyRecord: ProtoCodec<KeyRecord> = schema.getCodecForType('dxos.halo.keyring.KeyRecord');
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Shared public API for keyring implementations.
|
|
20
|
+
*/
|
|
21
|
+
export interface KeyringApi extends Signer {
|
|
22
|
+
readonly keysUpdate: Event;
|
|
23
|
+
createKey(): Promise<PublicKey>;
|
|
24
|
+
importKeyPair(keyPair: CryptoKeyPair): Promise<PublicKey>;
|
|
25
|
+
list(): Promise<KeyRecord[]>;
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Manages keys.
|
|
20
30
|
*/
|
|
21
|
-
export class Keyring implements
|
|
31
|
+
export class Keyring implements KeyringApi {
|
|
22
32
|
private readonly _keyCache = new ComplexMap<PublicKey, CryptoKeyPair>(PublicKey.hash);
|
|
23
33
|
readonly keysUpdate = new Event();
|
|
24
34
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as SqlClient from '@effect/sql/SqlClient';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
8
|
+
import * as ManagedRuntime from 'effect/ManagedRuntime';
|
|
9
|
+
import { describe, expect, onTestFinished, test } from 'vitest';
|
|
10
|
+
|
|
11
|
+
import { verifySignature } from '@dxos/crypto';
|
|
12
|
+
import { RuntimeProvider } from '@dxos/effect';
|
|
13
|
+
import { layerMemory as sqliteLayerMemory } from '@dxos/sql-sqlite/platform';
|
|
14
|
+
import * as SqlTransaction from '@dxos/sql-sqlite/SqlTransaction';
|
|
15
|
+
|
|
16
|
+
import { SqliteKeyring } from './sqlite-keyring';
|
|
17
|
+
|
|
18
|
+
const makeRuntime = () => {
|
|
19
|
+
const rt = ManagedRuntime.make(SqlTransaction.layer.pipe(Layer.provideMerge(sqliteLayerMemory)).pipe(Layer.orDie));
|
|
20
|
+
onTestFinished(() => rt.dispose());
|
|
21
|
+
return rt.runtimeEffect;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('SqliteKeyring', () => {
|
|
25
|
+
test('creates and verifies key', async () => {
|
|
26
|
+
const runtime = makeRuntime();
|
|
27
|
+
const keyring = new SqliteKeyring({ runtime });
|
|
28
|
+
|
|
29
|
+
// Run migration
|
|
30
|
+
await RuntimeProvider.runPromise(runtime)(keyring.migrate);
|
|
31
|
+
|
|
32
|
+
// Create key
|
|
33
|
+
const key = await keyring.createKey();
|
|
34
|
+
expect(key).toBeDefined();
|
|
35
|
+
|
|
36
|
+
// Sign and verify
|
|
37
|
+
const message = Buffer.from('hello');
|
|
38
|
+
const signature = await keyring.sign(key, message);
|
|
39
|
+
expect(await verifySignature(key, message, signature)).toBeTruthy();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('lists keys', async () => {
|
|
43
|
+
const runtime = makeRuntime();
|
|
44
|
+
const keyring = new SqliteKeyring({ runtime });
|
|
45
|
+
await RuntimeProvider.runPromise(runtime)(keyring.migrate);
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < 3; i++) {
|
|
48
|
+
await keyring.createKey();
|
|
49
|
+
}
|
|
50
|
+
const keys = await keyring.list();
|
|
51
|
+
expect(keys).toHaveLength(3);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('direct sql insert works', async () => {
|
|
55
|
+
const runtime = makeRuntime();
|
|
56
|
+
await RuntimeProvider.runPromise(runtime)(
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
const sql = yield* SqlClient.SqlClient;
|
|
59
|
+
yield* sql`CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, val TEXT)`;
|
|
60
|
+
yield* sql`INSERT INTO test_table (val) VALUES (${'hello'})`;
|
|
61
|
+
const rows = yield* sql<{ val: string }>`SELECT val FROM test_table`;
|
|
62
|
+
expect(rows[0].val).toBe('hello');
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as SqlClient from '@effect/sql/SqlClient';
|
|
6
|
+
import type * as SqlError from '@effect/sql/SqlError';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
|
|
9
|
+
import { Event, synchronized } from '@dxos/async';
|
|
10
|
+
import { subtleCrypto } from '@dxos/crypto';
|
|
11
|
+
import { RuntimeProvider } from '@dxos/effect';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
import { PublicKey } from '@dxos/keys';
|
|
14
|
+
import { schema } from '@dxos/protocols/proto';
|
|
15
|
+
import { type KeyRecord } from '@dxos/protocols/proto/dxos/halo/keyring';
|
|
16
|
+
import { SqlTransaction } from '@dxos/sql-sqlite';
|
|
17
|
+
import { ComplexMap, arrayToBuffer } from '@dxos/util';
|
|
18
|
+
|
|
19
|
+
import { type KeyringApi } from './keyring';
|
|
20
|
+
|
|
21
|
+
const KeyRecordCodec = schema.getCodecForType('dxos.halo.keyring.KeyRecord');
|
|
22
|
+
|
|
23
|
+
// SqlTransaction.SqlTransaction is the Tag class exported from the SqlTransaction namespace.
|
|
24
|
+
type SqlTransactionTag = SqlTransaction.SqlTransaction;
|
|
25
|
+
|
|
26
|
+
export type SqliteKeyringOptions = {
|
|
27
|
+
runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* SQLite-backed Keyring.
|
|
32
|
+
* Stores ECDSA key pairs in the `keyring` table.
|
|
33
|
+
*/
|
|
34
|
+
export class SqliteKeyring implements KeyringApi {
|
|
35
|
+
readonly #runtime: RuntimeProvider.RuntimeProvider<SqlClient.SqlClient | SqlTransactionTag>;
|
|
36
|
+
readonly #keyCache = new ComplexMap<PublicKey, CryptoKeyPair>(PublicKey.hash);
|
|
37
|
+
readonly keysUpdate = new Event();
|
|
38
|
+
|
|
39
|
+
constructor({ runtime }: SqliteKeyringOptions) {
|
|
40
|
+
invariant(subtleCrypto, 'SubtleCrypto not available in this environment.');
|
|
41
|
+
this.#runtime = runtime;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
readonly migrate: Effect.Effect<void, SqlError.SqlError, SqlClient.SqlClient | SqlTransactionTag> = Effect.fn(
|
|
45
|
+
'SqliteKeyring.migrate',
|
|
46
|
+
)(() =>
|
|
47
|
+
Effect.gen(function* () {
|
|
48
|
+
const sql = yield* SqlClient.SqlClient;
|
|
49
|
+
yield* sql`CREATE TABLE IF NOT EXISTS keyring (
|
|
50
|
+
public_key TEXT PRIMARY KEY,
|
|
51
|
+
record BLOB NOT NULL
|
|
52
|
+
)`;
|
|
53
|
+
}).pipe(Effect.withSpan('SqliteKeyring.migrate')),
|
|
54
|
+
)();
|
|
55
|
+
|
|
56
|
+
async sign(key: PublicKey, message: Uint8Array): Promise<Uint8Array> {
|
|
57
|
+
const keyPair = await this._getKey(key);
|
|
58
|
+
return new Uint8Array(
|
|
59
|
+
await subtleCrypto.sign(
|
|
60
|
+
{ name: 'ECDSA', hash: 'SHA-256' },
|
|
61
|
+
keyPair.privateKey,
|
|
62
|
+
message as Uint8Array<ArrayBuffer>,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async createKey(): Promise<PublicKey> {
|
|
68
|
+
const keyPair = await subtleCrypto.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']);
|
|
69
|
+
await this._setKey(keyPair);
|
|
70
|
+
return keyPairToPublicKey(keyPair);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async importKeyPair(keyPair: CryptoKeyPair): Promise<PublicKey> {
|
|
74
|
+
await this._setKey(keyPair);
|
|
75
|
+
return keyPairToPublicKey(keyPair);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async list(): Promise<KeyRecord[]> {
|
|
79
|
+
const rows = await RuntimeProvider.runPromise(this.#runtime)(
|
|
80
|
+
Effect.gen(function* () {
|
|
81
|
+
const sql = yield* SqlClient.SqlClient;
|
|
82
|
+
return yield* sql<{ record: Uint8Array }>`SELECT record FROM keyring`;
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
return rows.map((row) => {
|
|
86
|
+
const record = KeyRecordCodec.decode(row.record);
|
|
87
|
+
// Never expose private key material to callers.
|
|
88
|
+
return { publicKey: record.publicKey };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@synchronized
|
|
93
|
+
private async _getKey(key: PublicKey): Promise<CryptoKeyPair> {
|
|
94
|
+
if (this.#keyCache.has(key)) {
|
|
95
|
+
return this.#keyCache.get(key)!;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const keyHex = key.toHex();
|
|
99
|
+
const rows = await RuntimeProvider.runPromise(this.#runtime)(
|
|
100
|
+
Effect.gen(function* () {
|
|
101
|
+
const sql = yield* SqlClient.SqlClient;
|
|
102
|
+
return yield* sql<{ record: Uint8Array }>`SELECT record FROM keyring WHERE public_key = ${keyHex}`;
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (rows.length === 0) {
|
|
107
|
+
throw new Error(`Key not found: ${keyHex}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const record = KeyRecordCodec.decode(rows[0].record);
|
|
111
|
+
const publicKey = PublicKey.from(record.publicKey);
|
|
112
|
+
invariant(key.equals(publicKey), 'Corrupted keyring: key mismatch');
|
|
113
|
+
invariant(record.privateKey, 'Corrupted keyring: missing private key');
|
|
114
|
+
|
|
115
|
+
const keyPair: CryptoKeyPair = {
|
|
116
|
+
publicKey: await subtleCrypto.importKey(
|
|
117
|
+
'raw',
|
|
118
|
+
record.publicKey as Uint8Array<ArrayBuffer>,
|
|
119
|
+
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
120
|
+
true,
|
|
121
|
+
['verify'],
|
|
122
|
+
),
|
|
123
|
+
privateKey: await subtleCrypto.importKey(
|
|
124
|
+
'pkcs8',
|
|
125
|
+
record.privateKey as Uint8Array<ArrayBuffer>,
|
|
126
|
+
{ name: 'ECDSA', namedCurve: 'P-256' },
|
|
127
|
+
true,
|
|
128
|
+
['sign'],
|
|
129
|
+
),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
this.#keyCache.set(publicKey, keyPair);
|
|
133
|
+
return keyPair;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@synchronized
|
|
137
|
+
private async _setKey(keyPair: CryptoKeyPair): Promise<void> {
|
|
138
|
+
const publicKey = await keyPairToPublicKey(keyPair);
|
|
139
|
+
this.#keyCache.set(publicKey, keyPair);
|
|
140
|
+
|
|
141
|
+
const record: KeyRecord = {
|
|
142
|
+
publicKey: publicKey.asUint8Array(),
|
|
143
|
+
privateKey: new Uint8Array(await subtleCrypto.exportKey('pkcs8', keyPair.privateKey)),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const keyHex = publicKey.toHex();
|
|
147
|
+
const encodedRecord = arrayToBuffer(KeyRecordCodec.encode(record));
|
|
148
|
+
await RuntimeProvider.runPromise(this.#runtime)(
|
|
149
|
+
Effect.gen(function* () {
|
|
150
|
+
const sql = yield* SqlClient.SqlClient;
|
|
151
|
+
yield* sql`INSERT OR REPLACE INTO keyring (public_key, record) VALUES (${keyHex}, ${encodedRecord})`;
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
this.keysUpdate.emit();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const keyPairToPublicKey = async (keyPair: CryptoKeyPair): Promise<PublicKey> =>
|
|
159
|
+
PublicKey.from(new Uint8Array(await subtleCrypto.exportKey('raw', keyPair.publicKey)));
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import "@dxos/node-std/globals";
|
|
2
|
-
|
|
3
|
-
// src/keyring.ts
|
|
4
|
-
import { Event, synchronized } from "@dxos/async";
|
|
5
|
-
import { subtleCrypto } from "@dxos/crypto";
|
|
6
|
-
import { todo } from "@dxos/debug";
|
|
7
|
-
import { invariant } from "@dxos/invariant";
|
|
8
|
-
import { PublicKey } from "@dxos/keys";
|
|
9
|
-
import { schema } from "@dxos/protocols/proto";
|
|
10
|
-
import { StorageType, createStorage } from "@dxos/random-access-storage";
|
|
11
|
-
import { ComplexMap, arrayToBuffer } from "@dxos/util";
|
|
12
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
13
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
15
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
16
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
17
|
-
}
|
|
18
|
-
var __dxlog_file = "/__w/dxos/dxos/packages/core/halo/keyring/src/keyring.ts";
|
|
19
|
-
var KeyRecord = schema.getCodecForType("dxos.halo.keyring.KeyRecord");
|
|
20
|
-
var Keyring = class {
|
|
21
|
-
_storage;
|
|
22
|
-
_keyCache = new ComplexMap(PublicKey.hash);
|
|
23
|
-
keysUpdate = new Event();
|
|
24
|
-
constructor(_storage = createStorage({
|
|
25
|
-
type: StorageType.RAM
|
|
26
|
-
}).createDirectory("keyring")) {
|
|
27
|
-
this._storage = _storage;
|
|
28
|
-
invariant(subtleCrypto, "SubtleCrypto not available in this environment.", {
|
|
29
|
-
F: __dxlog_file,
|
|
30
|
-
L: 30,
|
|
31
|
-
S: this,
|
|
32
|
-
A: [
|
|
33
|
-
"subtleCrypto",
|
|
34
|
-
"'SubtleCrypto not available in this environment.'"
|
|
35
|
-
]
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
async sign(key, message) {
|
|
39
|
-
const keyPair = await this._getKey(key);
|
|
40
|
-
return new Uint8Array(await subtleCrypto.sign({
|
|
41
|
-
name: "ECDSA",
|
|
42
|
-
hash: "SHA-256"
|
|
43
|
-
}, keyPair.privateKey, message));
|
|
44
|
-
}
|
|
45
|
-
async createKey() {
|
|
46
|
-
const keyPair = await subtleCrypto.generateKey({
|
|
47
|
-
name: "ECDSA",
|
|
48
|
-
namedCurve: "P-256"
|
|
49
|
-
}, true, [
|
|
50
|
-
"sign",
|
|
51
|
-
"verify"
|
|
52
|
-
]);
|
|
53
|
-
await this._setKey(keyPair);
|
|
54
|
-
return keyPairToPublicKey(keyPair);
|
|
55
|
-
}
|
|
56
|
-
async _getKey(key) {
|
|
57
|
-
if (!this._keyCache.has(key)) {
|
|
58
|
-
const file = this._storage.getOrCreateFile(key.toHex());
|
|
59
|
-
const { size } = await file.stat();
|
|
60
|
-
if (size === 0) {
|
|
61
|
-
throw new Error(`Key not found: ${key.toHex()}`);
|
|
62
|
-
}
|
|
63
|
-
const recordBytes = await file.read(0, size);
|
|
64
|
-
await file.close();
|
|
65
|
-
const record = KeyRecord.decode(recordBytes);
|
|
66
|
-
const publicKey = PublicKey.from(record.publicKey);
|
|
67
|
-
invariant(key.equals(publicKey), "Corrupted keyring: Key mismatch", {
|
|
68
|
-
F: __dxlog_file,
|
|
69
|
-
L: 77,
|
|
70
|
-
S: this,
|
|
71
|
-
A: [
|
|
72
|
-
"key.equals(publicKey)",
|
|
73
|
-
"'Corrupted keyring: Key mismatch'"
|
|
74
|
-
]
|
|
75
|
-
});
|
|
76
|
-
invariant(record.privateKey, "Corrupted keyring: Missing private key", {
|
|
77
|
-
F: __dxlog_file,
|
|
78
|
-
L: 78,
|
|
79
|
-
S: this,
|
|
80
|
-
A: [
|
|
81
|
-
"record.privateKey",
|
|
82
|
-
"'Corrupted keyring: Missing private key'"
|
|
83
|
-
]
|
|
84
|
-
});
|
|
85
|
-
const keyPair = {
|
|
86
|
-
publicKey: await subtleCrypto.importKey("raw", record.publicKey, {
|
|
87
|
-
name: "ECDSA",
|
|
88
|
-
namedCurve: "P-256"
|
|
89
|
-
}, true, [
|
|
90
|
-
"verify"
|
|
91
|
-
]),
|
|
92
|
-
privateKey: await subtleCrypto.importKey("pkcs8", record.privateKey, {
|
|
93
|
-
name: "ECDSA",
|
|
94
|
-
namedCurve: "P-256"
|
|
95
|
-
}, true, [
|
|
96
|
-
"sign"
|
|
97
|
-
])
|
|
98
|
-
};
|
|
99
|
-
this._keyCache.set(publicKey, keyPair);
|
|
100
|
-
}
|
|
101
|
-
return this._keyCache.get(key);
|
|
102
|
-
}
|
|
103
|
-
async _setKey(keyPair) {
|
|
104
|
-
const publicKey = await keyPairToPublicKey(keyPair);
|
|
105
|
-
this._keyCache.set(publicKey, keyPair);
|
|
106
|
-
const record = {
|
|
107
|
-
publicKey: publicKey.asUint8Array(),
|
|
108
|
-
privateKey: new Uint8Array(await subtleCrypto.exportKey("pkcs8", keyPair.privateKey))
|
|
109
|
-
};
|
|
110
|
-
const file = this._storage.getOrCreateFile(publicKey.toHex());
|
|
111
|
-
await file.write(0, arrayToBuffer(KeyRecord.encode(record)));
|
|
112
|
-
await file.close();
|
|
113
|
-
await file.flush?.();
|
|
114
|
-
this.keysUpdate.emit();
|
|
115
|
-
}
|
|
116
|
-
// TODO(burdon): ???
|
|
117
|
-
deleteKey(key) {
|
|
118
|
-
return todo("We need a method to delete a file.");
|
|
119
|
-
}
|
|
120
|
-
async list() {
|
|
121
|
-
const keys = [];
|
|
122
|
-
for (const path of await this._storage.list()) {
|
|
123
|
-
const fileName = path.split("/").pop();
|
|
124
|
-
invariant(fileName, "Invalid file name", {
|
|
125
|
-
F: __dxlog_file,
|
|
126
|
-
L: 134,
|
|
127
|
-
S: this,
|
|
128
|
-
A: [
|
|
129
|
-
"fileName",
|
|
130
|
-
"'Invalid file name'"
|
|
131
|
-
]
|
|
132
|
-
});
|
|
133
|
-
keys.push({
|
|
134
|
-
publicKey: PublicKey.fromHex(fileName).asUint8Array()
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
return keys;
|
|
138
|
-
}
|
|
139
|
-
async importKeyPair(keyPair) {
|
|
140
|
-
await this._setKey(keyPair);
|
|
141
|
-
return keyPairToPublicKey(keyPair);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
_ts_decorate([
|
|
145
|
-
synchronized
|
|
146
|
-
], Keyring.prototype, "_getKey", null);
|
|
147
|
-
_ts_decorate([
|
|
148
|
-
synchronized
|
|
149
|
-
], Keyring.prototype, "_setKey", null);
|
|
150
|
-
var keyPairToPublicKey = async (keyPair) => {
|
|
151
|
-
return PublicKey.from(new Uint8Array(await subtleCrypto.exportKey("raw", keyPair.publicKey)));
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
// src/testing.ts
|
|
155
|
-
import { subtleCrypto as subtleCrypto2 } from "@dxos/crypto";
|
|
156
|
-
var generateJWKKeyPair = async () => {
|
|
157
|
-
const keyPair = await subtleCrypto2.generateKey({
|
|
158
|
-
name: "ECDSA",
|
|
159
|
-
namedCurve: "P-256"
|
|
160
|
-
}, true, [
|
|
161
|
-
"sign",
|
|
162
|
-
"verify"
|
|
163
|
-
]);
|
|
164
|
-
const privateKeyExported = await subtleCrypto2.exportKey("jwk", keyPair.privateKey);
|
|
165
|
-
const publicKeyExported = await subtleCrypto2.exportKey("jwk", keyPair.publicKey);
|
|
166
|
-
const publicKeyBuffer = new Uint8Array(await subtleCrypto2.exportKey("raw", keyPair.publicKey));
|
|
167
|
-
const publicKeyHex = Array.from(publicKeyBuffer).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
168
|
-
return {
|
|
169
|
-
privateKey: privateKeyExported,
|
|
170
|
-
publicKey: publicKeyExported,
|
|
171
|
-
publicKeyHex
|
|
172
|
-
};
|
|
173
|
-
};
|
|
174
|
-
var parseJWKKeyPair = async (privateKey, publicKey) => {
|
|
175
|
-
return {
|
|
176
|
-
privateKey: await subtleCrypto2.importKey("jwk", privateKey, {
|
|
177
|
-
name: "ECDSA",
|
|
178
|
-
namedCurve: "P-256"
|
|
179
|
-
}, true, [
|
|
180
|
-
"sign"
|
|
181
|
-
]),
|
|
182
|
-
publicKey: await subtleCrypto2.importKey("jwk", publicKey, {
|
|
183
|
-
name: "ECDSA",
|
|
184
|
-
namedCurve: "P-256"
|
|
185
|
-
}, true, [
|
|
186
|
-
"verify"
|
|
187
|
-
])
|
|
188
|
-
};
|
|
189
|
-
};
|
|
190
|
-
export {
|
|
191
|
-
Keyring,
|
|
192
|
-
generateJWKKeyPair,
|
|
193
|
-
parseJWKKeyPair
|
|
194
|
-
};
|
|
195
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/keyring.ts", "../../../src/testing.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { Event, synchronized } from '@dxos/async';\nimport { type ProtoCodec } from '@dxos/codec-protobuf';\nimport { type Signer, subtleCrypto } from '@dxos/crypto';\nimport { todo } from '@dxos/debug';\nimport { invariant } from '@dxos/invariant';\nimport { PublicKey } from '@dxos/keys';\nimport { schema } from '@dxos/protocols/proto';\nimport { type KeyRecord } from '@dxos/protocols/proto/dxos/halo/keyring';\nimport { type Directory, StorageType, createStorage } from '@dxos/random-access-storage';\nimport { ComplexMap, arrayToBuffer } from '@dxos/util';\n\nconst KeyRecord: ProtoCodec<KeyRecord> = schema.getCodecForType('dxos.halo.keyring.KeyRecord');\n\n/**\n * Manages keys.\n */\nexport class Keyring implements Signer {\n private readonly _keyCache = new ComplexMap<PublicKey, CryptoKeyPair>(PublicKey.hash);\n readonly keysUpdate = new Event();\n\n constructor(\n private readonly _storage: Directory = createStorage({\n type: StorageType.RAM,\n }).createDirectory('keyring'),\n ) {\n invariant(subtleCrypto, 'SubtleCrypto not available in this environment.');\n }\n\n async sign(key: PublicKey, message: Uint8Array): Promise<Uint8Array> {\n const keyPair = await this._getKey(key);\n\n return new Uint8Array(\n await subtleCrypto.sign(\n {\n name: 'ECDSA',\n hash: 'SHA-256',\n },\n keyPair.privateKey,\n message as Uint8Array<ArrayBuffer>,\n ),\n );\n }\n\n async createKey(): Promise<PublicKey> {\n const keyPair = await subtleCrypto.generateKey(\n {\n name: 'ECDSA',\n namedCurve: 'P-256',\n },\n true,\n ['sign', 'verify'],\n );\n\n await this._setKey(keyPair);\n\n return keyPairToPublicKey(keyPair);\n }\n\n @synchronized\n private async _getKey(key: PublicKey): Promise<CryptoKeyPair> {\n if (!this._keyCache.has(key)) {\n const file = this._storage.getOrCreateFile(key.toHex());\n const { size } = await file.stat();\n if (size === 0) {\n throw new Error(`Key not found: ${key.toHex()}`);\n }\n\n const recordBytes = await file.read(0, size);\n await file.close();\n\n const record = KeyRecord.decode(recordBytes);\n const publicKey = PublicKey.from(record.publicKey);\n invariant(key.equals(publicKey), 'Corrupted keyring: Key mismatch');\n invariant(record.privateKey, 'Corrupted keyring: Missing private key');\n const keyPair: CryptoKeyPair = {\n publicKey: await subtleCrypto.importKey(\n 'raw',\n record.publicKey as Uint8Array<ArrayBuffer>,\n {\n name: 'ECDSA',\n namedCurve: 'P-256',\n },\n true,\n ['verify'],\n ),\n privateKey: await subtleCrypto.importKey(\n 'pkcs8',\n record.privateKey as Uint8Array<ArrayBuffer>,\n {\n name: 'ECDSA',\n namedCurve: 'P-256',\n },\n true,\n ['sign'],\n ),\n };\n\n this._keyCache.set(publicKey, keyPair);\n }\n\n return this._keyCache.get(key)!; // TODO(burdon): Fail if null?\n }\n\n @synchronized\n private async _setKey(keyPair: CryptoKeyPair): Promise<void> {\n const publicKey = await keyPairToPublicKey(keyPair);\n this._keyCache.set(publicKey, keyPair);\n\n const record: KeyRecord = {\n publicKey: publicKey.asUint8Array(),\n privateKey: new Uint8Array(await subtleCrypto.exportKey('pkcs8', keyPair.privateKey)),\n };\n\n const file = this._storage.getOrCreateFile(publicKey.toHex());\n await file.write(0, arrayToBuffer(KeyRecord.encode(record)));\n await file.close();\n await file.flush?.();\n this.keysUpdate.emit();\n }\n\n // TODO(burdon): ???\n deleteKey(key: PublicKey): Promise<void> {\n return todo('We need a method to delete a file.');\n }\n\n async list(): Promise<KeyRecord[]> {\n const keys: KeyRecord[] = [];\n for (const path of await this._storage.list()) {\n const fileName = path.split('/').pop(); // get last portion of the path\n invariant(fileName, 'Invalid file name');\n keys.push({ publicKey: PublicKey.fromHex(fileName).asUint8Array() });\n }\n return keys;\n }\n\n async importKeyPair(keyPair: CryptoKeyPair): Promise<PublicKey> {\n await this._setKey(keyPair);\n return keyPairToPublicKey(keyPair);\n }\n}\n\nconst keyPairToPublicKey = async (keyPair: CryptoKeyPair): Promise<PublicKey> => {\n return PublicKey.from(new Uint8Array(await subtleCrypto.exportKey('raw', keyPair.publicKey)));\n};\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { subtleCrypto } from '@dxos/crypto';\n\nexport type TestKeyPair = {\n privateKey: JsonWebKey;\n publicKey: JsonWebKey;\n publicKeyHex: string;\n};\n\n/**\n * Generate a key pair which for testing purposes.\n * @returns {Promise<TestKeyPair>}\n */\nexport const generateJWKKeyPair = async (): Promise<TestKeyPair> => {\n const keyPair = await subtleCrypto.generateKey(\n {\n name: 'ECDSA',\n namedCurve: 'P-256',\n },\n true,\n ['sign', 'verify'],\n );\n\n const privateKeyExported = await subtleCrypto.exportKey('jwk', keyPair.privateKey);\n const publicKeyExported = await subtleCrypto.exportKey('jwk', keyPair.publicKey);\n\n // Convert the public key to hex format\n const publicKeyBuffer = new Uint8Array(await subtleCrypto.exportKey('raw', keyPair.publicKey));\n const publicKeyHex = Array.from(publicKeyBuffer)\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('');\n\n return {\n privateKey: privateKeyExported,\n publicKey: publicKeyExported,\n publicKeyHex,\n };\n};\n\n/**\n * Parse a key pair from JWK format.\n */\nexport const parseJWKKeyPair = async (privateKey: JsonWebKey, publicKey: JsonWebKey): Promise<CryptoKeyPair> => {\n return {\n privateKey: await subtleCrypto.importKey('jwk', privateKey, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign']),\n publicKey: await subtleCrypto.importKey('jwk', publicKey, { name: 'ECDSA', namedCurve: 'P-256' }, true, ['verify']),\n };\n};\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["Event", "synchronized", "subtleCrypto", "todo", "invariant", "PublicKey", "schema", "StorageType", "createStorage", "ComplexMap", "arrayToBuffer", "KeyRecord", "getCodecForType", "Keyring", "_keyCache", "hash", "keysUpdate", "_storage", "type", "RAM", "createDirectory", "sign", "key", "message", "keyPair", "_getKey", "Uint8Array", "name", "privateKey", "createKey", "generateKey", "namedCurve", "_setKey", "keyPairToPublicKey", "has", "file", "getOrCreateFile", "toHex", "size", "stat", "Error", "recordBytes", "read", "close", "record", "decode", "publicKey", "from", "equals", "importKey", "set", "get", "asUint8Array", "exportKey", "write", "encode", "flush", "emit", "deleteKey", "list", "keys", "path", "fileName", "split", "pop", "push", "fromHex", "importKeyPair", "subtleCrypto", "generateJWKKeyPair", "keyPair", "subtleCrypto", "generateKey", "name", "namedCurve", "privateKeyExported", "exportKey", "privateKey", "publicKeyExported", "publicKey", "publicKeyBuffer", "Uint8Array", "publicKeyHex", "Array", "from", "map", "byte", "toString", "padStart", "join", "parseJWKKeyPair", "importKey"]
|
|
7
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"inputs":{"src/keyring.ts":{"bytes":16719,"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/crypto","kind":"import-statement","external":true},{"path":"@dxos/debug","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/keys","kind":"import-statement","external":true},{"path":"@dxos/protocols/proto","kind":"import-statement","external":true},{"path":"@dxos/random-access-storage","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"src/testing.ts":{"bytes":5160,"imports":[{"path":"@dxos/crypto","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":539,"imports":[{"path":"src/keyring.ts","kind":"import-statement","original":"./keyring"},{"path":"src/testing.ts","kind":"import-statement","original":"./testing"}],"format":"esm"}},"outputs":{"dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":10614},"dist/lib/browser/index.mjs":{"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/crypto","kind":"import-statement","external":true},{"path":"@dxos/debug","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/keys","kind":"import-statement","external":true},{"path":"@dxos/protocols/proto","kind":"import-statement","external":true},{"path":"@dxos/random-access-storage","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@dxos/crypto","kind":"import-statement","external":true}],"exports":["Keyring","generateJWKKeyPair","parseJWKKeyPair"],"entryPoint":"src/index.ts","inputs":{"src/keyring.ts":{"bytesInOutput":4905},"src/index.ts":{"bytesInOutput":0},"src/testing.ts":{"bytesInOutput":1103}},"bytes":6177}}}
|