nervur 0.12.0 → 0.13.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/GETTING-STARTED.md +111 -0
- package/README.md +44 -18
- package/SECURITY.md +11 -0
- package/being.mjs +73 -33
- package/blueprint.mjs +13 -13
- package/client.mjs +9 -6
- package/contract.mjs +6 -4
- package/ground.mjs +95 -36
- package/index.mjs +2 -2
- package/modules.mjs +13 -70
- package/package.json +5 -2
- package/tools.mjs +67 -48
- package/voice.mjs +49 -63
package/voice.mjs
CHANGED
|
@@ -1,69 +1,63 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
createPublicKey,
|
|
5
|
-
diffieHellman,
|
|
6
|
-
generateKeyPairSync,
|
|
7
|
-
sign,
|
|
8
|
-
} from 'node:crypto';
|
|
9
|
-
import { Tools } from './tools.mjs';
|
|
1
|
+
import { ed25519, x25519 } from '@noble/curves/ed25519.js';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
3
|
+
import { Bytes, Tools } from './tools.mjs';
|
|
10
4
|
|
|
11
|
-
|
|
5
|
+
// A private key travels as PKCS8 DER in base64 — the shape every stored
|
|
6
|
+
// bundle and every hand on the wire already carries — and a PKCS8 Ed25519 or
|
|
7
|
+
// X25519 key is one fixed prefix and the thirty-two seed bytes, so the bytes
|
|
8
|
+
// are the same whoever assembles them.
|
|
9
|
+
const ED_PKCS8 = Bytes.unhex('302e020100300506032b657004220420');
|
|
10
|
+
const X_PKCS8 = Bytes.unhex('302e020100300506032b656e04220420');
|
|
12
11
|
|
|
13
|
-
const
|
|
14
|
-
privateKey.export({ format: 'der', type: 'pkcs8' }).toString('base64');
|
|
12
|
+
const seeded = () => crypto.getRandomValues(new Uint8Array(32));
|
|
15
13
|
|
|
16
|
-
const
|
|
17
|
-
createPrivateKey({ key: Buffer.from(text, 'base64'), format: 'der', type: 'pkcs8' });
|
|
14
|
+
const packed = (raw, prefix) => Bytes.base64(Bytes.concat([prefix, raw]));
|
|
18
15
|
|
|
19
|
-
const
|
|
20
|
-
|
|
16
|
+
const unpacked = (text, prefix) => {
|
|
17
|
+
const der = Bytes.unbase64(text);
|
|
18
|
+
if (der.length !== prefix.length + 32 || !Bytes.equal(der.subarray(0, prefix.length), prefix)) {
|
|
19
|
+
throw new Error('not a key of this curve');
|
|
20
|
+
}
|
|
21
|
+
return der.subarray(prefix.length);
|
|
22
|
+
};
|
|
21
23
|
|
|
22
|
-
const agreed = (
|
|
23
|
-
createHash('sha256')
|
|
24
|
-
.update(
|
|
25
|
-
diffieHellman({
|
|
26
|
-
privateKey,
|
|
27
|
-
publicKey: createPublicKey({
|
|
28
|
-
key: Buffer.concat([X25519_SPKI_PREFIX, rawPub]),
|
|
29
|
-
format: 'der',
|
|
30
|
-
type: 'spki',
|
|
31
|
-
}),
|
|
32
|
-
}),
|
|
33
|
-
)
|
|
34
|
-
.digest();
|
|
24
|
+
const agreed = (box, rawPub) => sha256(x25519.getSharedSecret(box, rawPub));
|
|
35
25
|
|
|
36
26
|
const committed = /^[0-9a-f]{64}$/;
|
|
37
27
|
|
|
38
28
|
const voiced = ({ present, next, box, nextPkHash }) =>
|
|
39
29
|
Object.freeze({
|
|
40
|
-
boxPk:
|
|
41
|
-
nextPkHash: next === undefined ? nextPkHash : Tools.digest(
|
|
42
|
-
open: (wrapped) => {
|
|
30
|
+
boxPk: x25519.getPublicKey(box),
|
|
31
|
+
nextPkHash: next === undefined ? nextPkHash : Tools.digest(ed25519.getPublicKey(next)),
|
|
32
|
+
open: (wrapped, cipher) => {
|
|
43
33
|
try {
|
|
44
34
|
const [ephemeral, sealed] = wrapped.split('.');
|
|
45
|
-
return
|
|
35
|
+
return cipher.unseal(agreed(box, Bytes.unbase64(ephemeral)), sealed);
|
|
46
36
|
} catch {
|
|
47
37
|
return undefined;
|
|
48
38
|
}
|
|
49
39
|
},
|
|
50
40
|
hand: () => ({
|
|
51
|
-
box: packed(box),
|
|
52
|
-
nextPkHash: next === undefined ? nextPkHash : Tools.digest(
|
|
53
|
-
present: packed(present),
|
|
41
|
+
box: packed(box, X_PKCS8),
|
|
42
|
+
nextPkHash: next === undefined ? nextPkHash : Tools.digest(ed25519.getPublicKey(next)),
|
|
43
|
+
present: packed(present, ED_PKCS8),
|
|
54
44
|
}),
|
|
55
45
|
pack: () =>
|
|
56
46
|
next === undefined
|
|
57
|
-
? { box: packed(box), nextPkHash, present: packed(present) }
|
|
58
|
-
: {
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
? { box: packed(box, X_PKCS8), nextPkHash, present: packed(present, ED_PKCS8) }
|
|
48
|
+
: {
|
|
49
|
+
box: packed(box, X_PKCS8),
|
|
50
|
+
next: packed(next, ED_PKCS8),
|
|
51
|
+
present: packed(present, ED_PKCS8),
|
|
52
|
+
},
|
|
53
|
+
pk: ed25519.getPublicKey(present),
|
|
54
|
+
sign: (bytes) => Bytes.base64(ed25519.sign(bytes, present)),
|
|
61
55
|
succeed: () =>
|
|
62
56
|
next === undefined
|
|
63
57
|
? undefined
|
|
64
58
|
: voiced({
|
|
65
|
-
box:
|
|
66
|
-
next:
|
|
59
|
+
box: seeded(),
|
|
60
|
+
next: seeded(),
|
|
67
61
|
present: next,
|
|
68
62
|
}),
|
|
69
63
|
});
|
|
@@ -72,18 +66,10 @@ const Voice = Object.freeze(
|
|
|
72
66
|
Object.assign(
|
|
73
67
|
(nextPkHash) => {
|
|
74
68
|
if (nextPkHash === undefined) {
|
|
75
|
-
return voiced({
|
|
76
|
-
box: generateKeyPairSync('x25519').privateKey,
|
|
77
|
-
next: generateKeyPairSync('ed25519').privateKey,
|
|
78
|
-
present: generateKeyPairSync('ed25519').privateKey,
|
|
79
|
-
});
|
|
69
|
+
return voiced({ box: seeded(), next: seeded(), present: seeded() });
|
|
80
70
|
}
|
|
81
71
|
return committed.test(nextPkHash)
|
|
82
|
-
? voiced({
|
|
83
|
-
box: generateKeyPairSync('x25519').privateKey,
|
|
84
|
-
nextPkHash,
|
|
85
|
-
present: generateKeyPairSync('ed25519').privateKey,
|
|
86
|
-
})
|
|
72
|
+
? voiced({ box: seeded(), nextPkHash, present: seeded() })
|
|
87
73
|
: undefined;
|
|
88
74
|
},
|
|
89
75
|
{
|
|
@@ -92,27 +78,27 @@ const Voice = Object.freeze(
|
|
|
92
78
|
if (bundle.next === undefined) {
|
|
93
79
|
return committed.test(bundle.nextPkHash)
|
|
94
80
|
? voiced({
|
|
95
|
-
box: unpacked(bundle.box),
|
|
81
|
+
box: unpacked(bundle.box, X_PKCS8),
|
|
96
82
|
nextPkHash: bundle.nextPkHash,
|
|
97
|
-
present: unpacked(bundle.present),
|
|
83
|
+
present: unpacked(bundle.present, ED_PKCS8),
|
|
98
84
|
})
|
|
99
85
|
: undefined;
|
|
100
86
|
}
|
|
101
87
|
return voiced({
|
|
102
|
-
box: unpacked(bundle.box),
|
|
103
|
-
next: unpacked(bundle.next),
|
|
104
|
-
present: unpacked(bundle.present),
|
|
88
|
+
box: unpacked(bundle.box, X_PKCS8),
|
|
89
|
+
next: unpacked(bundle.next, ED_PKCS8),
|
|
90
|
+
present: unpacked(bundle.present, ED_PKCS8),
|
|
105
91
|
});
|
|
106
92
|
} catch {
|
|
107
93
|
return undefined;
|
|
108
94
|
}
|
|
109
95
|
},
|
|
110
|
-
sealTo: (boxPk, value) => {
|
|
111
|
-
const ephemeral =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
96
|
+
sealTo: (boxPk, value, cipher) => {
|
|
97
|
+
const ephemeral = seeded();
|
|
98
|
+
const shut = cipher?.seal(agreed(ephemeral, boxPk), value);
|
|
99
|
+
return shut === undefined
|
|
100
|
+
? undefined
|
|
101
|
+
: `${Bytes.base64(x25519.getPublicKey(ephemeral))}.${shut}`;
|
|
116
102
|
},
|
|
117
103
|
},
|
|
118
104
|
),
|