@7h3/protocol 0.5.0 → 0.5.2
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/README.md +6 -1317
- package/agentAdapter.d.ts +26 -0
- package/auditLog.d.ts +54 -0
- package/capability.d.ts +67 -0
- package/cborCodec.d.ts +34 -0
- package/conformanceVectors.d.ts +20 -0
- package/encryption.d.ts +85 -0
- package/envelopeCbor.d.ts +33 -0
- package/frameworkAdapters.d.ts +72 -0
- package/gateway.d.ts +53 -0
- package/grpcBinding.d.ts +29 -0
- package/httpBinding.d.ts +50 -0
- package/index.d.ts +39 -0
- package/index.js +3670 -0
- package/keyInfra.d.ts +64 -0
- package/keyRegistry.d.ts +9 -0
- package/keyRotation.d.ts +20 -0
- package/mcpGateway.d.ts +37 -0
- package/mcpTransports.d.ts +62 -0
- package/mcpWrapper.d.ts +83 -0
- package/otel.d.ts +65 -0
- package/package.json +13 -121
- package/policyEnforcer.d.ts +50 -0
- package/policyTelemetryFeedback.d.ts +11 -0
- package/{src/protocol.d.ts → protocol.d.ts} +6 -2
- package/protocolAgent.d.ts +58 -0
- package/protocolBinary.d.ts +8 -0
- package/protocolCapabilities.d.ts +24 -0
- package/protocolReplay.d.ts +35 -0
- package/protocolTransport.d.ts +73 -0
- package/queueBinding.d.ts +43 -0
- package/rateLimiter.d.ts +18 -0
- package/{src/redisClient.ts → redisClient.d.ts} +25 -75
- package/replayStores.d.ts +98 -0
- package/revocation.d.ts +71 -0
- package/routePolicy.d.ts +30 -0
- package/runtimePolicy.d.ts +24 -0
- package/runtimePolicyManager.d.ts +15 -0
- package/runtimePolicyPresets.d.ts +11 -0
- package/signedResponse.d.ts +21 -0
- package/stream.d.ts +59 -0
- package/telemetry.d.ts +79 -0
- package/webhookBinding.d.ts +30 -0
- package/wsBinding.d.ts +52 -0
- package/.dockerignore +0 -19
- package/.github/dependabot.yml +0 -32
- package/.github/workflows/ci.yml +0 -31
- package/.github/workflows/publish.yml +0 -59
- package/.github/workflows/scorecard.yml +0 -37
- package/7h3.example.yaml +0 -125
- package/CHANGELOG.md +0 -152
- package/CONTRIBUTING.md +0 -82
- package/Dockerfile +0 -73
- package/GOVERNANCE.md +0 -62
- package/SECURITY.md +0 -70
- package/bench-results/replay-cache-full-1777891033256.json +0 -10
- package/bench-results/replay-cache-full-1777896317488.json +0 -10
- package/bench-results/replay-cache-full-1777900993184.json +0 -10
- package/bench-results/replay-cache-full-1777901019285.json +0 -10
- package/bench-results/replay-cache-quick-1777870170126.json +0 -10
- package/bench-results/signature-profiles-quick-1775875160079.json +0 -85
- package/bench-results/signature-profiles-quick-1775983539716.json +0 -85
- package/bench-results/signature-profiles-quick-1776237913190.json +0 -85
- package/bench-results/wire-codecs-full-1777891019803.json +0 -93
- package/bench-results/wire-codecs-full-1777896260964.json +0 -93
- package/bench-results/wire-codecs-full-1777901004247.json +0 -93
- package/bench-results/wire-codecs-quick-1775972879056.json +0 -93
- package/bench-results/wire-codecs-quick-1775983541111.json +0 -93
- package/bench-results/wire-codecs-quick-1776237914299.json +0 -93
- package/bench-results/wire-codecs-quick-1777841285236.json +0 -93
- package/bench-results/wire-codecs-quick-1777841321772.json +0 -93
- package/bench-results/wire-codecs-quick-1777841330408.json +0 -93
- package/bench-results/wire-codecs-quick-1777852886082.json +0 -93
- package/bench-results/wire-codecs-quick-1777852988773.json +0 -93
- package/bench-results/wire-codecs-quick-1777870188095.json +0 -93
- package/bench-results/wire-codecs-quick-1777870263918.json +0 -93
- package/bench-results/wire-codecs-quick-1777870455034.json +0 -93
- package/bench-results/wire-codecs-quick-1778816163081.json +0 -93
- package/bench-results/wire-codecs-quick-1778843936130.json +0 -93
- package/bin/7h3.ts +0 -406
- package/conformance/7h3_v0_1.json +0 -77
- package/conformance/7h3_v0_1_binary.json +0 -20
- package/conformance/aip_v0_1_binary.json +0 -20
- package/docker-compose.yaml +0 -77
- package/docs/ADOPTION_PLAN.md +0 -120
- package/docs/AGENTS.md +0 -77
- package/docs/AIP_RFC_v0.1.md +0 -97
- package/docs/AI_DECISION_CARD.md +0 -122
- package/docs/AI_RUNTIME_POLICY.json +0 -126
- package/docs/AI_RUNTIME_POLICY.yaml +0 -110
- package/docs/BACKPRESSURE_TUNING.md +0 -65
- package/docs/BENCHMARK_CLAIM_MATRIX.md +0 -42
- package/docs/BENCHMARK_REPORT_TEMPLATE.md +0 -169
- package/docs/BINARY_CODEC_BENCH.md +0 -23
- package/docs/CLEAN_CLONE_RUNBOOK.md +0 -36
- package/docs/CLOCK_SKEW_POLICY.md +0 -30
- package/docs/DISTRIBUTED_REPLAY.md +0 -142
- package/docs/FUZZ_CAMPAIGN.md +0 -121
- package/docs/GATEWAY.md +0 -195
- package/docs/KEY_MANAGEMENT_POLICY.md +0 -53
- package/docs/KEY_REVOCATION.md +0 -69
- package/docs/MCP_WRAPPER.md +0 -159
- package/docs/MIGRATION_GUIDE.md +0 -40
- package/docs/OPERATORS.md +0 -184
- package/docs/PERF_REGRESSION_POLICY.md +0 -34
- package/docs/PROJECT_EXAMINATION_2026-05-31.md +0 -219
- package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +0 -135
- package/docs/RELEASE_GATE.md +0 -25
- package/docs/RELEASE_NOTES_v0.1.0.md +0 -54
- package/docs/SECURITY_REVIEW_2026-06-05.md +0 -165
- package/docs/TELEMETRY.md +0 -41
- package/docs/THREAT_MODEL.md +0 -89
- package/docs/VERSIONING_POLICY.md +0 -30
- package/docs/assets/banner-github.png +0 -0
- package/docs/assets/banner.png +0 -0
- package/docs/assets/banner.svg +0 -123
- package/eslint.config.js +0 -15
- package/fuzz/ts/harness-decode.ts +0 -136
- package/fuzz/ts/harness-verify.ts +0 -121
- package/fuzz/ts/run.ts +0 -35
- package/mcp-server/README.md +0 -38
- package/mcp-server/package-lock.json +0 -1187
- package/mcp-server/package.json +0 -35
- package/mcp-server/src/index.ts +0 -236
- package/mcp-server/tsconfig.json +0 -14
- package/scripts/aip-framework-quickstart.ts +0 -110
- package/scripts/aip-mcp-gateway.ts +0 -38
- package/scripts/aip-mcp-wrap-demo.ts +0 -72
- package/scripts/aip-quickstart.ts +0 -60
- package/scripts/bench-diff.ts +0 -118
- package/scripts/bench-protocol-e2e.ts +0 -937
- package/scripts/bench-protocol-openloop.ts +0 -1397
- package/scripts/bench-replay-cache.ts +0 -76
- package/scripts/bench-signature-profiles.ts +0 -180
- package/scripts/bench-wire-codecs.ts +0 -161
- package/scripts/build-binary-conformance.ts +0 -36
- package/scripts/build-release-dashboard.ts +0 -175
- package/scripts/canary-rollout.ts +0 -38
- package/scripts/mcpGatewayCli.test.ts +0 -116
- package/scripts/prepare-aip-package.ts +0 -88
- package/scripts/regen-conformance-sigs.ts +0 -18
- package/scripts/release-gate.ts +0 -19
- package/scripts/validate-runtime-policy.ts +0 -18
- package/sdk/browser/index.test.ts +0 -162
- package/sdk/browser/index.ts +0 -257
- package/sdk/browser/package.json +0 -13
- package/sdk/go/cbor.go +0 -551
- package/sdk/go/cbor_test.go +0 -232
- package/sdk/go/encryption.go +0 -280
- package/sdk/go/encryption_test.go +0 -318
- package/sdk/go/go.mod +0 -7
- package/sdk/go/go.sum +0 -4
- package/sdk/go/http.go +0 -135
- package/sdk/go/protocol.go +0 -324
- package/sdk/go/protocol_test.go +0 -334
- package/sdk/go/replay.go +0 -121
- package/sdk/go/replay_test.go +0 -149
- package/sdk/go/webhook.go +0 -136
- package/sdk/pq/package-lock.json +0 -1358
- package/sdk/pq/package.json +0 -42
- package/sdk/pq/src/index.test.ts +0 -143
- package/sdk/pq/src/index.ts +0 -166
- package/sdk/pq/tsconfig.json +0 -14
- package/sdk/pq/vitest.config.ts +0 -7
- package/sdk/python/README.md +0 -18
- package/sdk/python/protocol_7h3/__init__.py +0 -46
- package/sdk/python/protocol_7h3/encryption.py +0 -252
- package/sdk/python/protocol_7h3/http.py +0 -212
- package/sdk/python/protocol_7h3/keys.py +0 -149
- package/sdk/python/protocol_7h3/pq.py +0 -244
- package/sdk/python/protocol_7h3/protocol.py +0 -525
- package/sdk/python/protocol_7h3/queue.py +0 -118
- package/sdk/python/protocol_7h3/replay.py +0 -98
- package/sdk/python/protocol_7h3/webhook.py +0 -116
- package/sdk/python/pyproject.toml +0 -40
- package/sdk/python/tests/test_conformance.py +0 -110
- package/sdk/python/tests/test_encryption.py +0 -206
- package/sdk/python/tests/test_http.py +0 -305
- package/sdk/python/tests/test_keys.py +0 -417
- package/sdk/python/tests/test_queue.py +0 -120
- package/sdk/python/tests/test_webhook.py +0 -345
- package/sdk/rust/Cargo.lock +0 -371
- package/sdk/rust/Cargo.toml +0 -25
- package/sdk/rust/README.md +0 -31
- package/sdk/rust/fuzz/Cargo.toml +0 -29
- package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +0 -46
- package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +0 -11
- package/sdk/rust/src/bin/aip_mcp_gateway.rs +0 -59
- package/sdk/rust/src/http.rs +0 -145
- package/sdk/rust/src/keys.rs +0 -161
- package/sdk/rust/src/lib.rs +0 -688
- package/sdk/rust/src/queue.rs +0 -79
- package/sdk/rust/src/webhook.rs +0 -86
- package/sdk/rust/tests/conformance.rs +0 -148
- package/sdk/rust/tests/gateway.rs +0 -130
- package/sdk/rust/tests/http_webhook_queue.rs +0 -201
- package/sdk/rust/tests/keys.rs +0 -189
- package/sdk/threshold/index.d.ts +0 -68
- package/sdk/threshold/index.d.ts.map +0 -1
- package/sdk/threshold/index.js +0 -254
- package/sdk/threshold/package-lock.json +0 -1361
- package/sdk/threshold/package.json +0 -39
- package/sdk/threshold/src/index.d.ts +0 -68
- package/sdk/threshold/src/index.d.ts.map +0 -1
- package/sdk/threshold/src/index.js +0 -254
- package/sdk/threshold/src/index.test.ts +0 -238
- package/sdk/threshold/src/index.ts +0 -355
- package/sdk/threshold/tsconfig.json +0 -19
- package/sdk/threshold/vitest.config.ts +0 -12
- package/src/agentAdapter.test.ts +0 -48
- package/src/agentAdapter.ts +0 -56
- package/src/auditLog.test.ts +0 -145
- package/src/auditLog.ts +0 -147
- package/src/capability.test.ts +0 -504
- package/src/capability.ts +0 -380
- package/src/cborCodec.test.ts +0 -263
- package/src/cborCodec.ts +0 -339
- package/src/conformance.test.ts +0 -136
- package/src/conformanceVectors.ts +0 -99
- package/src/encryption.test.ts +0 -206
- package/src/encryption.ts +0 -245
- package/src/envelopeCbor.ts +0 -140
- package/src/frameworkAdapters.test.ts +0 -290
- package/src/frameworkAdapters.ts +0 -261
- package/src/gateway.test.ts +0 -343
- package/src/gateway.ts +0 -246
- package/src/grpcBinding.test.ts +0 -211
- package/src/grpcBinding.ts +0 -103
- package/src/httpBinding.test.ts +0 -376
- package/src/httpBinding.ts +0 -189
- package/src/index.ts +0 -39
- package/src/keyInfra.test.ts +0 -278
- package/src/keyInfra.ts +0 -228
- package/src/keyRegistry.ts +0 -59
- package/src/keyRotation.test.ts +0 -78
- package/src/keyRotation.ts +0 -72
- package/src/mcpGateway.test.ts +0 -129
- package/src/mcpGateway.ts +0 -250
- package/src/mcpTransports.test.ts +0 -92
- package/src/mcpTransports.ts +0 -169
- package/src/mcpWrapper.test.ts +0 -179
- package/src/mcpWrapper.ts +0 -206
- package/src/otel.ts +0 -136
- package/src/policyEnforcer.test.ts +0 -99
- package/src/policyEnforcer.ts +0 -169
- package/src/policyTelemetryFeedback.test.ts +0 -25
- package/src/policyTelemetryFeedback.ts +0 -38
- package/src/protocol.bench.ts +0 -37
- package/src/protocol.d.ts.map +0 -1
- package/src/protocol.js +0 -294
- package/src/protocol.test.ts +0 -155
- package/src/protocol.ts +0 -414
- package/src/protocolAgent.test.ts +0 -105
- package/src/protocolAgent.ts +0 -169
- package/src/protocolBinary.test.ts +0 -165
- package/src/protocolBinary.ts +0 -312
- package/src/protocolCapabilities.ts +0 -70
- package/src/protocolFuzz.advanced.test.ts +0 -235
- package/src/protocolFuzz.test.ts +0 -111
- package/src/protocolNegative.test.ts +0 -97
- package/src/protocolReplay.test.ts +0 -71
- package/src/protocolReplay.ts +0 -194
- package/src/protocolTransport.test.ts +0 -556
- package/src/protocolTransport.ts +0 -483
- package/src/queueBinding.test.ts +0 -130
- package/src/queueBinding.ts +0 -102
- package/src/rateLimiter.test.ts +0 -96
- package/src/rateLimiter.ts +0 -46
- package/src/redisIntegration.test.ts +0 -134
- package/src/replayStores.test.ts +0 -273
- package/src/replayStores.ts +0 -215
- package/src/revocation.test.ts +0 -98
- package/src/revocation.ts +0 -0
- package/src/routePolicy.test.ts +0 -87
- package/src/routePolicy.ts +0 -72
- package/src/runtimePolicy.test.ts +0 -49
- package/src/runtimePolicy.ts +0 -81
- package/src/runtimePolicyManager.test.ts +0 -29
- package/src/runtimePolicyManager.ts +0 -50
- package/src/runtimePolicyPresets.ts +0 -43
- package/src/signedResponse.test.ts +0 -111
- package/src/signedResponse.ts +0 -83
- package/src/stream.test.ts +0 -254
- package/src/stream.ts +0 -417
- package/src/telemetry.test.ts +0 -251
- package/src/telemetry.ts +0 -299
- package/src/webhookBinding.test.ts +0 -144
- package/src/webhookBinding.ts +0 -115
- package/src/wsBinding.test.ts +0 -221
- package/src/wsBinding.ts +0 -200
- package/tsconfig.json +0 -15
- package/tsconfig.lib.json +0 -23
- package/vite.lib.config.ts +0 -16
- package/vitest.config.ts +0 -11
package/src/encryption.test.ts
DELETED
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { createEnvelope, generateEd25519KeypairBase64Url, type ProtocolBody } from './protocol.js'
|
|
3
|
-
import {
|
|
4
|
-
decryptBody,
|
|
5
|
-
encryptBody,
|
|
6
|
-
generateX25519KeyPair,
|
|
7
|
-
openEnvelope,
|
|
8
|
-
sealEnvelope,
|
|
9
|
-
} from './encryption.js'
|
|
10
|
-
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// Helpers
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
function makeEnvelope(body: ProtocolBody) {
|
|
16
|
-
return createEnvelope({
|
|
17
|
-
sender: 'agent-alice',
|
|
18
|
-
recipient: 'agent-bob',
|
|
19
|
-
intent: body.intent,
|
|
20
|
-
content: body.content,
|
|
21
|
-
capability: body.capability,
|
|
22
|
-
correlationId: body.correlationId,
|
|
23
|
-
ttlMs: 60_000,
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Tests
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
describe('generateX25519KeyPair', () => {
|
|
32
|
-
it('returns 32-byte base64url keys (~43 chars, no padding)', () => {
|
|
33
|
-
const kp = generateX25519KeyPair()
|
|
34
|
-
// base64url of 32 bytes = ceil(32 * 4/3) = 43 chars (no padding)
|
|
35
|
-
expect(kp.publicKey).toMatch(/^[A-Za-z0-9_-]{43}$/)
|
|
36
|
-
expect(kp.privateKey).toMatch(/^[A-Za-z0-9_-]{43}$/)
|
|
37
|
-
// Decoded must be exactly 32 bytes
|
|
38
|
-
expect(Buffer.from(kp.publicKey, 'base64url').length).toBe(32)
|
|
39
|
-
expect(Buffer.from(kp.privateKey, 'base64url').length).toBe(32)
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
describe('sealEnvelope + openEnvelope', () => {
|
|
44
|
-
it('round-trip recovers original body exactly', async () => {
|
|
45
|
-
const recipientKp = generateX25519KeyPair()
|
|
46
|
-
const senderEd = await generateEd25519KeypairBase64Url()
|
|
47
|
-
|
|
48
|
-
const originalBody: ProtocolBody = {
|
|
49
|
-
intent: 'TASK',
|
|
50
|
-
content: 'Hello encrypted world!',
|
|
51
|
-
capability: 'some-cap',
|
|
52
|
-
correlationId: 'corr-123',
|
|
53
|
-
}
|
|
54
|
-
const envelope = makeEnvelope(originalBody)
|
|
55
|
-
|
|
56
|
-
const sealed = await sealEnvelope(envelope, {
|
|
57
|
-
recipientX25519PublicKey: recipientKp.publicKey,
|
|
58
|
-
senderEd25519PrivateKey: senderEd.privateKey,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const { body } = await openEnvelope(sealed, {
|
|
62
|
-
recipientX25519PrivateKey: recipientKp.privateKey,
|
|
63
|
-
senderEd25519PublicKey: senderEd.publicKey,
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
expect(body.intent).toBe(originalBody.intent)
|
|
67
|
-
expect(body.content).toBe(originalBody.content)
|
|
68
|
-
expect(body.capability).toBe(originalBody.capability)
|
|
69
|
-
expect(body.correlationId).toBe(originalBody.correlationId)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('fails with wrong recipient private key (AEAD tag mismatch)', async () => {
|
|
73
|
-
const recipientKp = generateX25519KeyPair()
|
|
74
|
-
const wrongKp = generateX25519KeyPair()
|
|
75
|
-
const senderEd = await generateEd25519KeypairBase64Url()
|
|
76
|
-
|
|
77
|
-
// Sign with correct recipient public key but try to open with wrong private key
|
|
78
|
-
// To bypass the signature check we need to seal with wrongKp pubkey too,
|
|
79
|
-
// but the spec says "wrong recipient key" meaning sealed to correct pub key but opened with wrong priv.
|
|
80
|
-
const envelope = makeEnvelope({ intent: 'PING', content: 'secret' })
|
|
81
|
-
const sealed = await sealEnvelope(envelope, {
|
|
82
|
-
recipientX25519PublicKey: recipientKp.publicKey,
|
|
83
|
-
senderEd25519PrivateKey: senderEd.privateKey,
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// We must tamper with the encrypted content to force the wrong key path,
|
|
87
|
-
// but the signature would fail. Instead, bypass openEnvelope and call decryptBody directly
|
|
88
|
-
// with the wrong key — which is what the spec tests.
|
|
89
|
-
expect(() => decryptBody(sealed.body.content, wrongKp.privateKey)).toThrow()
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('fails if envelope signature is tampered', async () => {
|
|
93
|
-
const recipientKp = generateX25519KeyPair()
|
|
94
|
-
const senderEd = await generateEd25519KeypairBase64Url()
|
|
95
|
-
|
|
96
|
-
const envelope = makeEnvelope({ intent: 'PING', content: 'secret' })
|
|
97
|
-
const sealed = await sealEnvelope(envelope, {
|
|
98
|
-
recipientX25519PublicKey: recipientKp.publicKey,
|
|
99
|
-
senderEd25519PrivateKey: senderEd.privateKey,
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
// Tamper with the signature value
|
|
103
|
-
const tampered = {
|
|
104
|
-
...sealed,
|
|
105
|
-
signature: {
|
|
106
|
-
...sealed.signature!,
|
|
107
|
-
value: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
108
|
-
},
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
await expect(
|
|
112
|
-
openEnvelope(tampered, {
|
|
113
|
-
recipientX25519PrivateKey: recipientKp.privateKey,
|
|
114
|
-
senderEd25519PublicKey: senderEd.publicKey,
|
|
115
|
-
}),
|
|
116
|
-
).rejects.toThrow('signature verification failed')
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('fails if ciphertext is tampered (AEAD auth tag fails)', async () => {
|
|
120
|
-
const recipientKp = generateX25519KeyPair()
|
|
121
|
-
const senderEd = await generateEd25519KeypairBase64Url()
|
|
122
|
-
|
|
123
|
-
const envelope = makeEnvelope({ intent: 'PING', content: 'secret' })
|
|
124
|
-
const sealed = await sealEnvelope(envelope, {
|
|
125
|
-
recipientX25519PublicKey: recipientKp.publicKey,
|
|
126
|
-
senderEd25519PrivateKey: senderEd.privateKey,
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
// Decode the encrypted payload, flip a bit in ciphertext, re-encode
|
|
130
|
-
const payloadJson = Buffer.from(sealed.body.content, 'base64url').toString('utf8')
|
|
131
|
-
const payload = JSON.parse(payloadJson) as { ephemeralPublic: string; nonce: string; ciphertext: string; tag: string }
|
|
132
|
-
const ctBuf = Buffer.from(payload.ciphertext, 'base64url')
|
|
133
|
-
ctBuf[0] ^= 0xff // flip bits in first byte
|
|
134
|
-
const tamperedPayload = { ...payload, ciphertext: ctBuf.toString('base64url') }
|
|
135
|
-
const tamperedContent = Buffer.from(JSON.stringify(tamperedPayload), 'utf8').toString('base64url')
|
|
136
|
-
|
|
137
|
-
// We need to re-sign with the same key for the tamper to get past sig verification
|
|
138
|
-
// Instead: call decryptBody directly with tampered content
|
|
139
|
-
expect(() => decryptBody(tamperedContent, recipientKp.privateKey)).toThrow()
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('two sealEnvelope calls on same body produce different ciphertexts (ephemeral randomness)', async () => {
|
|
143
|
-
const recipientKp = generateX25519KeyPair()
|
|
144
|
-
const senderEd = await generateEd25519KeypairBase64Url()
|
|
145
|
-
|
|
146
|
-
const envelope1 = makeEnvelope({ intent: 'PING', content: 'same content' })
|
|
147
|
-
const envelope2 = makeEnvelope({ intent: 'PING', content: 'same content' })
|
|
148
|
-
|
|
149
|
-
const sealed1 = await sealEnvelope(envelope1, {
|
|
150
|
-
recipientX25519PublicKey: recipientKp.publicKey,
|
|
151
|
-
senderEd25519PrivateKey: senderEd.privateKey,
|
|
152
|
-
})
|
|
153
|
-
const sealed2 = await sealEnvelope(envelope2, {
|
|
154
|
-
recipientX25519PublicKey: recipientKp.publicKey,
|
|
155
|
-
senderEd25519PrivateKey: senderEd.privateKey,
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
// Different ephemeral keys + different nonces → different ciphertexts
|
|
159
|
-
expect(sealed1.body.content).not.toBe(sealed2.body.content)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('encrypted content is opaque (does not contain original body.content as plaintext)', async () => {
|
|
163
|
-
const recipientKp = generateX25519KeyPair()
|
|
164
|
-
const senderEd = await generateEd25519KeypairBase64Url()
|
|
165
|
-
|
|
166
|
-
const originalContent = 'super-secret-data-12345'
|
|
167
|
-
const envelope = makeEnvelope({ intent: 'TASK', content: originalContent })
|
|
168
|
-
|
|
169
|
-
const sealed = await sealEnvelope(envelope, {
|
|
170
|
-
recipientX25519PublicKey: recipientKp.publicKey,
|
|
171
|
-
senderEd25519PrivateKey: senderEd.privateKey,
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
// The encrypted content blob should not contain the original plaintext string
|
|
175
|
-
const encryptedContentDecoded = Buffer.from(sealed.body.content, 'base64url').toString('utf8')
|
|
176
|
-
expect(encryptedContentDecoded).not.toContain(originalContent)
|
|
177
|
-
// Also raw base64url should not contain it
|
|
178
|
-
expect(sealed.body.content).not.toContain(originalContent)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('encrypted envelope body has correct structure', async () => {
|
|
182
|
-
const recipientKp = generateX25519KeyPair()
|
|
183
|
-
const senderEd = await generateEd25519KeypairBase64Url()
|
|
184
|
-
|
|
185
|
-
const envelope = makeEnvelope({ intent: 'PING', content: 'test' })
|
|
186
|
-
const sealed = await sealEnvelope(envelope, {
|
|
187
|
-
recipientX25519PublicKey: recipientKp.publicKey,
|
|
188
|
-
senderEd25519PrivateKey: senderEd.privateKey,
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
expect(sealed.body.intent).toBe('ENCRYPTED')
|
|
192
|
-
expect(sealed.body.capability).toBe('x25519-chacha20poly1305')
|
|
193
|
-
expect(sealed.signature).toBeDefined()
|
|
194
|
-
expect(sealed.signature?.alg).toBe('ED25519')
|
|
195
|
-
})
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
describe('encryptBody + decryptBody', () => {
|
|
199
|
-
it('round-trips a body with all optional fields', () => {
|
|
200
|
-
const kp = generateX25519KeyPair()
|
|
201
|
-
const body: ProtocolBody = { intent: 'RESULT', content: 'data', capability: 'cap', correlationId: 'cid-42' }
|
|
202
|
-
const { encryptedContent } = encryptBody(body, kp.publicKey)
|
|
203
|
-
const decrypted = decryptBody(encryptedContent, kp.privateKey)
|
|
204
|
-
expect(decrypted).toEqual(body)
|
|
205
|
-
})
|
|
206
|
-
})
|
package/src/encryption.ts
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* E2E Encryption for 7h3 Protocol
|
|
3
|
-
*
|
|
4
|
-
* Uses X25519 Diffie-Hellman key exchange + ChaCha20-Poly1305 AEAD.
|
|
5
|
-
* All operations via Node.js built-in `node:crypto` — zero new dependencies.
|
|
6
|
-
*
|
|
7
|
-
* Architecture:
|
|
8
|
-
* EncryptedEnvelope = SignedEnvelope where body.content is a base64url-encoded
|
|
9
|
-
* EncryptedPayload, body.intent = 'ENCRYPTED', body.capability = 'x25519-chacha20poly1305'
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { createCipheriv, createDecipheriv, createPrivateKey, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes } from 'node:crypto'
|
|
13
|
-
import type { KeyObject } from 'node:crypto'
|
|
14
|
-
import {
|
|
15
|
-
signEnvelopeEd25519,
|
|
16
|
-
verifyEnvelopeEd25519,
|
|
17
|
-
type ProtocolBody,
|
|
18
|
-
type ProtocolEnvelope,
|
|
19
|
-
} from './protocol.js'
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Types
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
export interface X25519KeyPair {
|
|
26
|
-
/** Raw 32-byte X25519 public key, base64url-encoded (no padding) */
|
|
27
|
-
publicKey: string
|
|
28
|
-
/** Raw 32-byte X25519 private key, base64url-encoded (no padding) */
|
|
29
|
-
privateKey: string
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface EncryptedPayload {
|
|
33
|
-
/** Ephemeral X25519 public key (base64url, raw 32 bytes) */
|
|
34
|
-
ephemeralPublic: string
|
|
35
|
-
/** ChaCha20 nonce / HKDF salt (base64url, 12 bytes) */
|
|
36
|
-
nonce: string
|
|
37
|
-
/** Ciphertext without auth tag (base64url) */
|
|
38
|
-
ciphertext: string
|
|
39
|
-
/** 16-byte Poly1305 auth tag (base64url) */
|
|
40
|
-
tag: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// Key helpers
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
function toBase64Url(buf: Buffer | Uint8Array): string {
|
|
48
|
-
return Buffer.from(buf).toString('base64url')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function fromBase64Url(s: string): Buffer {
|
|
52
|
-
return Buffer.from(s, 'base64url')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* X25519 PKCS8 DER header: 30 2e 02 01 00 30 05 06 03 2b 65 6e 04 22 04 20
|
|
57
|
-
* (RFC 5958 / RFC 8410 encoding for X25519, OID 1.3.101.110 = 2b 65 6e)
|
|
58
|
-
*/
|
|
59
|
-
const X25519_PKCS8_HEADER = Buffer.from('302e020100300506032b656e04220420', 'hex')
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* X25519 SPKI DER header: 30 2a 30 05 06 03 2b 65 6e 03 21 00
|
|
63
|
-
* (RFC 5480 SubjectPublicKeyInfo for X25519)
|
|
64
|
-
*/
|
|
65
|
-
const X25519_SPKI_HEADER = Buffer.from('302a300506032b656e032100', 'hex')
|
|
66
|
-
|
|
67
|
-
function importX25519Private(privRaw32Base64Url: string): KeyObject {
|
|
68
|
-
const rawBytes = fromBase64Url(privRaw32Base64Url)
|
|
69
|
-
// Build PKCS8 DER: fixed 16-byte header + 32-byte raw private key
|
|
70
|
-
const der = Buffer.concat([X25519_PKCS8_HEADER, rawBytes])
|
|
71
|
-
return createPrivateKey({ key: der, format: 'der', type: 'pkcs8' })
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function importX25519Public(pubRaw32Base64Url: string): KeyObject {
|
|
75
|
-
const rawBytes = fromBase64Url(pubRaw32Base64Url)
|
|
76
|
-
// Build SPKI DER: fixed 12-byte header + 32-byte raw public key
|
|
77
|
-
const der = Buffer.concat([X25519_SPKI_HEADER, rawBytes])
|
|
78
|
-
return createPublicKey({ key: der, format: 'der', type: 'spki' })
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// Public API
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Generate a fresh X25519 keypair.
|
|
87
|
-
* Both keys are raw 32-byte values encoded as base64url (no padding).
|
|
88
|
-
*/
|
|
89
|
-
export function generateX25519KeyPair(): X25519KeyPair {
|
|
90
|
-
const { privateKey, publicKey } = generateKeyPairSync('x25519')
|
|
91
|
-
const privJwk = privateKey.export({ format: 'jwk' }) as { d: string }
|
|
92
|
-
const pubJwk = publicKey.export({ format: 'jwk' }) as { x: string }
|
|
93
|
-
return {
|
|
94
|
-
publicKey: pubJwk.x,
|
|
95
|
-
privateKey: privJwk.d,
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Perform X25519 DH + HKDF-SHA256 to derive a 32-byte ChaCha20-Poly1305 key.
|
|
101
|
-
*
|
|
102
|
-
* @param privateKeyBase64Url - raw 32-byte X25519 private key (base64url)
|
|
103
|
-
* @param peerPublicKeyBase64Url - raw 32-byte X25519 public key (base64url)
|
|
104
|
-
* @param nonce - raw 12-byte nonce (base64url); used as HKDF salt
|
|
105
|
-
* @returns 32-byte Buffer ready for use with createCipheriv/createDecipheriv
|
|
106
|
-
*/
|
|
107
|
-
export function deriveEncryptionKey(
|
|
108
|
-
privateKeyBase64Url: string,
|
|
109
|
-
peerPublicKeyBase64Url: string,
|
|
110
|
-
nonce: string,
|
|
111
|
-
): Buffer {
|
|
112
|
-
const privKey = importX25519Private(privateKeyBase64Url)
|
|
113
|
-
const pubKey = importX25519Public(peerPublicKeyBase64Url)
|
|
114
|
-
|
|
115
|
-
const sharedSecret = diffieHellman({ privateKey: privKey, publicKey: pubKey })
|
|
116
|
-
const salt = fromBase64Url(nonce)
|
|
117
|
-
const info = Buffer.from('7h3-enc/1', 'utf8')
|
|
118
|
-
|
|
119
|
-
const derived = hkdfSync('sha256', sharedSecret, salt, info, 32)
|
|
120
|
-
return Buffer.from(derived)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Encrypt a ProtocolBody with the recipient's X25519 public key.
|
|
125
|
-
*
|
|
126
|
-
* @returns encryptedContent (base64url-encoded EncryptedPayload JSON) and ephemeralPublic
|
|
127
|
-
*/
|
|
128
|
-
export function encryptBody(
|
|
129
|
-
body: ProtocolBody,
|
|
130
|
-
recipientX25519PublicKey: string,
|
|
131
|
-
): { encryptedContent: string; ephemeralPublic: string } {
|
|
132
|
-
// 1. Generate ephemeral X25519 keypair for forward secrecy
|
|
133
|
-
const ephemeral = generateX25519KeyPair()
|
|
134
|
-
|
|
135
|
-
// 2. Generate 12-byte ChaCha nonce (also used as HKDF salt)
|
|
136
|
-
const nonce12 = randomBytes(12)
|
|
137
|
-
const nonceBase64Url = toBase64Url(nonce12)
|
|
138
|
-
|
|
139
|
-
// 3. Derive encryption key
|
|
140
|
-
const key = deriveEncryptionKey(ephemeral.privateKey, recipientX25519PublicKey, nonceBase64Url)
|
|
141
|
-
|
|
142
|
-
// 4. Encrypt body as JSON
|
|
143
|
-
const plaintext = Buffer.from(JSON.stringify(body), 'utf8')
|
|
144
|
-
const cipher = createCipheriv('chacha20-poly1305', key, nonce12, { authTagLength: 16 })
|
|
145
|
-
const ciphertextBuf = Buffer.concat([cipher.update(plaintext), cipher.final()])
|
|
146
|
-
const tag = cipher.getAuthTag()
|
|
147
|
-
|
|
148
|
-
// 5. Build EncryptedPayload
|
|
149
|
-
const payload: EncryptedPayload = {
|
|
150
|
-
ephemeralPublic: ephemeral.publicKey,
|
|
151
|
-
nonce: nonceBase64Url,
|
|
152
|
-
ciphertext: toBase64Url(ciphertextBuf),
|
|
153
|
-
tag: toBase64Url(tag),
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
encryptedContent: toBase64Url(Buffer.from(JSON.stringify(payload), 'utf8')),
|
|
158
|
-
ephemeralPublic: ephemeral.publicKey,
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Decrypt an encrypted ProtocolBody.
|
|
164
|
-
*
|
|
165
|
-
* @param encryptedContent - base64url-encoded EncryptedPayload
|
|
166
|
-
* @param recipientX25519PrivateKey - raw 32-byte X25519 private key (base64url)
|
|
167
|
-
* @returns decrypted ProtocolBody
|
|
168
|
-
* @throws if AEAD tag verification fails
|
|
169
|
-
*/
|
|
170
|
-
export function decryptBody(encryptedContent: string, recipientX25519PrivateKey: string): ProtocolBody {
|
|
171
|
-
const payloadJson = fromBase64Url(encryptedContent).toString('utf8')
|
|
172
|
-
const payload = JSON.parse(payloadJson) as EncryptedPayload
|
|
173
|
-
|
|
174
|
-
const { ephemeralPublic, nonce, ciphertext, tag } = payload
|
|
175
|
-
|
|
176
|
-
// Derive the same key using recipient's private key + ephemeral public key
|
|
177
|
-
const key = deriveEncryptionKey(recipientX25519PrivateKey, ephemeralPublic, nonce)
|
|
178
|
-
|
|
179
|
-
const nonce12 = fromBase64Url(nonce)
|
|
180
|
-
const ciphertextBuf = fromBase64Url(ciphertext)
|
|
181
|
-
const tagBuf = fromBase64Url(tag)
|
|
182
|
-
|
|
183
|
-
const decipher = createDecipheriv('chacha20-poly1305', key, nonce12, { authTagLength: 16 })
|
|
184
|
-
decipher.setAuthTag(tagBuf)
|
|
185
|
-
|
|
186
|
-
const plaintext = Buffer.concat([decipher.update(ciphertextBuf), decipher.final()])
|
|
187
|
-
return JSON.parse(plaintext.toString('utf8')) as ProtocolBody
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Encrypt and sign an envelope (full pipeline).
|
|
192
|
-
*
|
|
193
|
-
* The original body is encrypted; the envelope body is replaced with:
|
|
194
|
-
* { intent: 'ENCRYPTED', content: <encrypted-payload>, capability: 'x25519-chacha20poly1305' }
|
|
195
|
-
* The modified envelope is then signed with Ed25519.
|
|
196
|
-
*/
|
|
197
|
-
export async function sealEnvelope(
|
|
198
|
-
envelope: Omit<ProtocolEnvelope, 'signature'>,
|
|
199
|
-
opts: {
|
|
200
|
-
recipientX25519PublicKey: string
|
|
201
|
-
senderEd25519PrivateKey: string
|
|
202
|
-
},
|
|
203
|
-
): Promise<ProtocolEnvelope> {
|
|
204
|
-
const { encryptedContent } = encryptBody(envelope.body, opts.recipientX25519PublicKey)
|
|
205
|
-
|
|
206
|
-
const encryptedEnvelope: Omit<ProtocolEnvelope, 'signature'> = {
|
|
207
|
-
header: envelope.header,
|
|
208
|
-
body: {
|
|
209
|
-
intent: 'ENCRYPTED' as const,
|
|
210
|
-
content: encryptedContent,
|
|
211
|
-
capability: 'x25519-chacha20poly1305',
|
|
212
|
-
correlationId: envelope.body.correlationId,
|
|
213
|
-
},
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Sign the envelope containing the encrypted body
|
|
217
|
-
return signEnvelopeEd25519(encryptedEnvelope, opts.senderEd25519PrivateKey)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Verify signature and decrypt an envelope (full pipeline).
|
|
222
|
-
*
|
|
223
|
-
* Signature is verified FIRST; decryption only proceeds if valid.
|
|
224
|
-
*
|
|
225
|
-
* @returns The signed envelope (with encrypted body) and the decrypted original body
|
|
226
|
-
* @throws if Ed25519 signature invalid or AEAD tag fails
|
|
227
|
-
*/
|
|
228
|
-
export async function openEnvelope(
|
|
229
|
-
envelope: ProtocolEnvelope,
|
|
230
|
-
opts: {
|
|
231
|
-
recipientX25519PrivateKey: string
|
|
232
|
-
senderEd25519PublicKey: string
|
|
233
|
-
},
|
|
234
|
-
): Promise<{ envelope: ProtocolEnvelope; body: ProtocolBody }> {
|
|
235
|
-
// 1. Verify Ed25519 signature FIRST — reject before decrypting
|
|
236
|
-
const valid = await verifyEnvelopeEd25519(envelope, opts.senderEd25519PublicKey)
|
|
237
|
-
if (!valid) {
|
|
238
|
-
throw new Error('7h3/encryption: Ed25519 signature verification failed')
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// 2. Decrypt body
|
|
242
|
-
const body = decryptBody(envelope.body.content, opts.recipientX25519PrivateKey)
|
|
243
|
-
|
|
244
|
-
return { envelope, body }
|
|
245
|
-
}
|
package/src/envelopeCbor.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Higher-level CBOR codec for ProtocolEnvelope.
|
|
3
|
-
* Uses numeric field keys for maximum compactness.
|
|
4
|
-
*
|
|
5
|
-
* Envelope map structure:
|
|
6
|
-
* 1 → header map
|
|
7
|
-
* 2 → body map
|
|
8
|
-
* 3 → signature map (omitted if no signature)
|
|
9
|
-
*
|
|
10
|
-
* Header map:
|
|
11
|
-
* 1 → version (string)
|
|
12
|
-
* 2 → messageId (string)
|
|
13
|
-
* 3 → timestampMs (int)
|
|
14
|
-
* 4 → ttlMs (int)
|
|
15
|
-
* 5 → sender (string)
|
|
16
|
-
* 6 → recipient (string, omit if absent)
|
|
17
|
-
* 7 → nonce (string)
|
|
18
|
-
*
|
|
19
|
-
* Body map:
|
|
20
|
-
* 1 → intent (string)
|
|
21
|
-
* 2 → content (string)
|
|
22
|
-
* 3 → capability (string, omit if absent)
|
|
23
|
-
* 4 → correlationId (string, omit if absent)
|
|
24
|
-
*
|
|
25
|
-
* Signature map:
|
|
26
|
-
* 1 → alg (string)
|
|
27
|
-
* 2 → keyId (string)
|
|
28
|
-
* 3 → value (string — base64url signature)
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
import type { ProtocolEnvelope, ProtocolSignature } from './protocol'
|
|
32
|
-
import { encodeCbor, decodeCbor } from './cborCodec'
|
|
33
|
-
|
|
34
|
-
export const CBOR_CONTENT_TYPE = 'application/7h3-cbor'
|
|
35
|
-
|
|
36
|
-
export function encodeEnvelopeCbor(env: ProtocolEnvelope): Uint8Array {
|
|
37
|
-
// Build header map
|
|
38
|
-
const headerMap = new Map<number, unknown>()
|
|
39
|
-
headerMap.set(1, env.header.version)
|
|
40
|
-
headerMap.set(2, env.header.messageId)
|
|
41
|
-
headerMap.set(3, env.header.timestampMs)
|
|
42
|
-
headerMap.set(4, env.header.ttlMs)
|
|
43
|
-
headerMap.set(5, env.header.sender)
|
|
44
|
-
if (env.header.recipient !== undefined) {
|
|
45
|
-
headerMap.set(6, env.header.recipient)
|
|
46
|
-
}
|
|
47
|
-
headerMap.set(7, env.header.nonce)
|
|
48
|
-
|
|
49
|
-
// Build body map
|
|
50
|
-
const bodyMap = new Map<number, unknown>()
|
|
51
|
-
bodyMap.set(1, env.body.intent)
|
|
52
|
-
bodyMap.set(2, env.body.content)
|
|
53
|
-
if (env.body.capability !== undefined) {
|
|
54
|
-
bodyMap.set(3, env.body.capability)
|
|
55
|
-
}
|
|
56
|
-
if (env.body.correlationId !== undefined) {
|
|
57
|
-
bodyMap.set(4, env.body.correlationId)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Build top-level map
|
|
61
|
-
const topMap = new Map<number, unknown>()
|
|
62
|
-
topMap.set(1, headerMap)
|
|
63
|
-
topMap.set(2, bodyMap)
|
|
64
|
-
|
|
65
|
-
const sig = env.signature
|
|
66
|
-
if (sig) {
|
|
67
|
-
const sigMap = new Map<number, unknown>()
|
|
68
|
-
sigMap.set(1, sig.alg)
|
|
69
|
-
sigMap.set(2, sig.keyId)
|
|
70
|
-
sigMap.set(3, sig.value)
|
|
71
|
-
topMap.set(3, sigMap)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return encodeCbor(topMap)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function decodeEnvelopeCbor(data: Uint8Array): ProtocolEnvelope {
|
|
78
|
-
const top = decodeCbor(data)
|
|
79
|
-
|
|
80
|
-
if (!top || typeof top !== 'object' || Array.isArray(top)) {
|
|
81
|
-
throw new Error('decodeEnvelopeCbor: expected a map at top level')
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const topRecord = top as Record<string, unknown>
|
|
85
|
-
|
|
86
|
-
// Header
|
|
87
|
-
const headerRaw = topRecord['1']
|
|
88
|
-
if (!headerRaw || typeof headerRaw !== 'object' || Array.isArray(headerRaw)) {
|
|
89
|
-
throw new Error('decodeEnvelopeCbor: missing header map (key 1)')
|
|
90
|
-
}
|
|
91
|
-
const hr = headerRaw as Record<string, unknown>
|
|
92
|
-
|
|
93
|
-
// Body
|
|
94
|
-
const bodyRaw = topRecord['2']
|
|
95
|
-
if (!bodyRaw || typeof bodyRaw !== 'object' || Array.isArray(bodyRaw)) {
|
|
96
|
-
throw new Error('decodeEnvelopeCbor: missing body map (key 2)')
|
|
97
|
-
}
|
|
98
|
-
const br = bodyRaw as Record<string, unknown>
|
|
99
|
-
|
|
100
|
-
const env: ProtocolEnvelope = {
|
|
101
|
-
header: {
|
|
102
|
-
version: hr['1'] as ProtocolEnvelope['header']['version'],
|
|
103
|
-
messageId: hr['2'] as string,
|
|
104
|
-
timestampMs: hr['3'] as number,
|
|
105
|
-
ttlMs: hr['4'] as number,
|
|
106
|
-
sender: hr['5'] as string,
|
|
107
|
-
nonce: hr['7'] as string,
|
|
108
|
-
},
|
|
109
|
-
body: {
|
|
110
|
-
intent: br['1'] as ProtocolEnvelope['body']['intent'],
|
|
111
|
-
content: br['2'] as string,
|
|
112
|
-
},
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (hr['6'] !== undefined) {
|
|
116
|
-
env.header.recipient = hr['6'] as string
|
|
117
|
-
}
|
|
118
|
-
if (br['3'] !== undefined) {
|
|
119
|
-
env.body.capability = br['3'] as string
|
|
120
|
-
}
|
|
121
|
-
if (br['4'] !== undefined) {
|
|
122
|
-
env.body.correlationId = br['4'] as string
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Signature
|
|
126
|
-
const sigRaw = topRecord['3']
|
|
127
|
-
if (sigRaw !== undefined && sigRaw !== null) {
|
|
128
|
-
if (typeof sigRaw !== 'object' || Array.isArray(sigRaw)) {
|
|
129
|
-
throw new Error('decodeEnvelopeCbor: expected signature map at key 3')
|
|
130
|
-
}
|
|
131
|
-
const sr = sigRaw as Record<string, unknown>
|
|
132
|
-
env.signature = {
|
|
133
|
-
alg: sr['1'] as ProtocolSignature['alg'],
|
|
134
|
-
keyId: sr['2'] as string,
|
|
135
|
-
value: sr['3'] as string,
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return env
|
|
140
|
-
}
|