@oxy.so/contracts 2.1.0 → 3.0.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/.tsbuildinfo +1 -1
- package/dist/cjs/accountEmail.js +79 -0
- package/dist/cjs/deviceBoot.js +2 -2
- package/dist/cjs/deviceSession.js +73 -16
- package/dist/cjs/identity.js +1 -3
- package/dist/cjs/identityLink.js +92 -0
- package/dist/cjs/identityProof.js +6 -29
- package/dist/cjs/index.js +82 -65
- package/dist/cjs/reputation.js +10 -55
- package/dist/cjs/signIn.js +304 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/accountEmail.js +76 -0
- package/dist/esm/deviceBoot.js +2 -2
- package/dist/esm/deviceSession.js +72 -15
- package/dist/esm/identity.js +1 -3
- package/dist/esm/identityLink.js +87 -0
- package/dist/esm/identityProof.js +6 -29
- package/dist/esm/index.js +13 -16
- package/dist/esm/reputation.js +9 -54
- package/dist/esm/signIn.js +299 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/accountEmail.d.ts +90 -0
- package/dist/types/agency.d.ts +2 -2
- package/dist/types/deviceBoot.d.ts +2 -2
- package/dist/types/deviceSession.d.ts +131 -19
- package/dist/types/externalIdentity.d.ts +4 -4
- package/dist/types/identity.d.ts +4 -8
- package/dist/types/identityLink.d.ts +146 -0
- package/dist/types/identityProof.d.ts +15 -42
- package/dist/types/index.d.ts +10 -12
- package/dist/types/inference/accountBilling.d.ts +2 -2
- package/dist/types/inference/embeddings.d.ts +16 -16
- package/dist/types/inference/entitlement.d.ts +2 -2
- package/dist/types/inference/errors.d.ts +8 -8
- package/dist/types/inference/inbox.d.ts +8 -8
- package/dist/types/inference/providerConnection.d.ts +20 -20
- package/dist/types/inference/request.d.ts +16 -16
- package/dist/types/inference/streamEvents.d.ts +20 -20
- package/dist/types/keyRecovery.d.ts +4 -4
- package/dist/types/oauth.d.ts +16 -16
- package/dist/types/reputation.d.ts +32 -127
- package/dist/types/sessionStatus.d.ts +2 -2
- package/dist/types/signIn.d.ts +717 -0
- package/dist/types/userResponse.d.ts +2 -2
- package/package.json +1 -1
- package/dist/cjs/identityMove.js +0 -156
- package/dist/cjs/identityRecovery.js +0 -51
- package/dist/cjs/webIdentityCarrier.js +0 -181
- package/dist/cjs/webauthn.js +0 -85
- package/dist/esm/identityMove.js +0 -148
- package/dist/esm/identityRecovery.js +0 -48
- package/dist/esm/webIdentityCarrier.js +0 -178
- package/dist/esm/webauthn.js +0 -82
- package/dist/types/identityMove.d.ts +0 -185
- package/dist/types/identityRecovery.d.ts +0 -246
- package/dist/types/webIdentityCarrier.d.ts +0 -1130
- package/dist/types/webauthn.d.ts +0 -285
|
@@ -969,6 +969,7 @@ export declare const currentUserResponseSchema: z.ZodObject<{
|
|
|
969
969
|
}, "strip", z.ZodTypeAny, {
|
|
970
970
|
data: {
|
|
971
971
|
name: UserNameResponse;
|
|
972
|
+
email?: string | undefined;
|
|
972
973
|
kind?: "bot" | "personal" | "organization" | "project" | "channel" | undefined;
|
|
973
974
|
username?: string | undefined;
|
|
974
975
|
avatar?: string | null | undefined;
|
|
@@ -980,7 +981,6 @@ export declare const currentUserResponseSchema: z.ZodObject<{
|
|
|
980
981
|
did?: string | undefined;
|
|
981
982
|
verifiedDomains?: import("./identity").VerifiedDomain[] | undefined;
|
|
982
983
|
_id?: string | undefined;
|
|
983
|
-
email?: string | undefined;
|
|
984
984
|
phone?: string | undefined;
|
|
985
985
|
address?: string | undefined;
|
|
986
986
|
birthday?: string | undefined;
|
|
@@ -995,6 +995,7 @@ export declare const currentUserResponseSchema: z.ZodObject<{
|
|
|
995
995
|
}, {
|
|
996
996
|
data: {
|
|
997
997
|
name: UserNameResponse;
|
|
998
|
+
email?: string | undefined;
|
|
998
999
|
kind?: "bot" | "personal" | "organization" | "project" | "channel" | undefined;
|
|
999
1000
|
username?: string | undefined;
|
|
1000
1001
|
avatar?: string | null | undefined;
|
|
@@ -1006,7 +1007,6 @@ export declare const currentUserResponseSchema: z.ZodObject<{
|
|
|
1006
1007
|
did?: string | undefined;
|
|
1007
1008
|
verifiedDomains?: import("./identity").VerifiedDomain[] | undefined;
|
|
1008
1009
|
_id?: string | undefined;
|
|
1009
|
-
email?: string | undefined;
|
|
1010
1010
|
phone?: string | undefined;
|
|
1011
1011
|
address?: string | undefined;
|
|
1012
1012
|
birthday?: string | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxy.so/contracts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "OxyHQ API contracts — single source of truth for request/response Zod schemas and inferred types, shared by the backend and the client SDKs",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
package/dist/cjs/identityMove.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.identityMoveStateSchema = exports.identityMoveReceiptRequestSchema = exports.identityMoveSealRequestSchema = exports.identityMoveRevealRequestSchema = exports.identityMoveJoinRequestSchema = exports.identityMoveCreateResponseSchema = exports.identityMoveCreateRequestSchema = exports.IDENTITY_MOVE_QR_PREFIX = exports.IDENTITY_MOVE_STATUSES = exports.identityMoveEphemeralKeySchema = exports.identityMoveIdSchema = exports.IDENTITY_MOVE_TTL_MS = void 0;
|
|
4
|
-
exports.buildMoveCommitmentInput = buildMoveCommitmentInput;
|
|
5
|
-
exports.buildMoveSasInput = buildMoveSasInput;
|
|
6
|
-
exports.buildMoveCiphertextDigestInput = buildMoveCiphertextDigestInput;
|
|
7
|
-
exports.buildMoveSealPayload = buildMoveSealPayload;
|
|
8
|
-
exports.buildMoveReceiptMessage = buildMoveReceiptMessage;
|
|
9
|
-
/**
|
|
10
|
-
* Identity move contract — give a web root to Commons (add it, or keep it only in
|
|
11
|
-
* Commons; ADR 0024 D6).
|
|
12
|
-
*
|
|
13
|
-
* The web (holding the root) shows a QR carrying only a move id; Commons scans.
|
|
14
|
-
* The relay in between must not be able to take the root, so:
|
|
15
|
-
*
|
|
16
|
-
* 1. `POST /identity/move { initiatorCommitment }` — the web publishes only
|
|
17
|
-
* `H(initiatorKey, nonce)`, never the key.
|
|
18
|
-
* 2. `POST /identity/move/:moveId/join { responderEphemeralPublicKey }` —
|
|
19
|
-
* Commons, having read the commitment first, joins with its own key.
|
|
20
|
-
* 3. `POST /identity/move/:moveId/reveal` — only then does the web reveal its
|
|
21
|
-
* key and nonce; Commons checks them against the commitment it read.
|
|
22
|
-
* 4. Both screens show a 6-digit code over `(moveId, both keys, commitment)`;
|
|
23
|
-
* the person confirms on the web that they match.
|
|
24
|
-
* 5. `POST /identity/move/:moveId/seal` — the web seals the phrase entropy under
|
|
25
|
-
* `HKDF(ECDH(both keys), moveId)`, authorized by a one-use root proof over the
|
|
26
|
-
* exact sealed bytes.
|
|
27
|
-
* 6. `POST /identity/move/:moveId/receipt` — Commons stores the root, reads it
|
|
28
|
-
* back from its keychain, and signs a receipt over the move, the root, both
|
|
29
|
-
* keys and the digest of the ciphertext it opened. The server verifies it; the
|
|
30
|
-
* web verifies it again from what IT sealed before removing anything.
|
|
31
|
-
*
|
|
32
|
-
* Why the commitment: without it the relay sees both keys before committing to
|
|
33
|
-
* anything and can grind a substituted key until the two codes agree (~10⁶
|
|
34
|
-
* tries, seconds). With it, the relay must fix the key it shows Commons before
|
|
35
|
-
* learning Commons' key, and the key it shows the web before the web reveals.
|
|
36
|
-
*
|
|
37
|
-
* The server holds a commitment, two ephemeral public keys, an opaque ciphertext
|
|
38
|
-
* and a receipt — nothing that decrypts the ciphertext.
|
|
39
|
-
*
|
|
40
|
-
* Platform-agnostic — zod only, ESM-safe (no `require()`).
|
|
41
|
-
*/
|
|
42
|
-
const zod_1 = require("zod");
|
|
43
|
-
const identityProof_1 = require("./identityProof");
|
|
44
|
-
/** A move lives this long: one interactive handoff. */
|
|
45
|
-
exports.IDENTITY_MOVE_TTL_MS = 5 * 60 * 1000;
|
|
46
|
-
/** 128-bit move id, lowercase hex. */
|
|
47
|
-
exports.identityMoveIdSchema = zod_1.z
|
|
48
|
-
.string()
|
|
49
|
-
.trim()
|
|
50
|
-
.regex(/^[0-9a-f]{32}$/, 'moveId must be 32 lowercase hex characters');
|
|
51
|
-
/** An ephemeral secp256k1 public key, uncompressed lowercase hex. */
|
|
52
|
-
exports.identityMoveEphemeralKeySchema = zod_1.z
|
|
53
|
-
.string()
|
|
54
|
-
.trim()
|
|
55
|
-
.regex(/^04[0-9a-f]{128}$/, 'ephemeral public key must be uncompressed, lowercase hex');
|
|
56
|
-
exports.IDENTITY_MOVE_STATUSES = ['pending', 'joined', 'sealed', 'completed', 'cancelled', 'expired'];
|
|
57
|
-
/** The QR payload Commons scans. Carries the move id only. */
|
|
58
|
-
exports.IDENTITY_MOVE_QR_PREFIX = 'oxycommons://move?id=';
|
|
59
|
-
const hex64 = (label) => zod_1.z.string().trim().regex(/^[0-9a-f]{64}$/, `${label} must be 64 lowercase hex characters`);
|
|
60
|
-
exports.identityMoveCreateRequestSchema = zod_1.z
|
|
61
|
-
.object({
|
|
62
|
-
/** `H(initiator ephemeral key, nonce)` — the key itself is revealed only after Commons joins. */
|
|
63
|
-
initiatorCommitment: hex64('initiatorCommitment'),
|
|
64
|
-
})
|
|
65
|
-
.strict();
|
|
66
|
-
exports.identityMoveCreateResponseSchema = zod_1.z.object({
|
|
67
|
-
moveId: exports.identityMoveIdSchema,
|
|
68
|
-
expiresAt: zod_1.z.string().datetime(),
|
|
69
|
-
});
|
|
70
|
-
exports.identityMoveJoinRequestSchema = zod_1.z.object({
|
|
71
|
-
responderEphemeralPublicKey: exports.identityMoveEphemeralKeySchema,
|
|
72
|
-
});
|
|
73
|
-
/** After Commons joined: the committed key and its nonce. */
|
|
74
|
-
exports.identityMoveRevealRequestSchema = zod_1.z
|
|
75
|
-
.object({
|
|
76
|
-
initiatorEphemeralPublicKey: exports.identityMoveEphemeralKeySchema,
|
|
77
|
-
commitmentNonce: hex64('commitmentNonce'),
|
|
78
|
-
})
|
|
79
|
-
.strict();
|
|
80
|
-
exports.identityMoveSealRequestSchema = zod_1.z
|
|
81
|
-
.object({
|
|
82
|
-
/** 24-byte XChaCha20-Poly1305 nonce, hex. */
|
|
83
|
-
nonce: zod_1.z.string().trim().regex(/^[0-9a-f]{48}$/, 'nonce must be 48 lowercase hex characters'),
|
|
84
|
-
/** The 16–32-byte phrase entropy (12–24 words), tag appended, hex. */
|
|
85
|
-
ciphertext: zod_1.z
|
|
86
|
-
.string()
|
|
87
|
-
.trim()
|
|
88
|
-
.regex(/^(?:[0-9a-f]{64}|[0-9a-f]{72}|[0-9a-f]{80}|[0-9a-f]{88}|[0-9a-f]{96})$/, 'ciphertext has an unsupported length'),
|
|
89
|
-
/**
|
|
90
|
-
* Root proof (`identity_move_seal`), payload `{ moveId, nonce, ciphertext }`,
|
|
91
|
-
* over a one-use challenge from `POST /identity/proof-challenge`.
|
|
92
|
-
*/
|
|
93
|
-
proof: identityProof_1.identityProofSchema,
|
|
94
|
-
})
|
|
95
|
-
.strict();
|
|
96
|
-
exports.identityMoveReceiptRequestSchema = zod_1.z
|
|
97
|
-
.object({
|
|
98
|
-
/** Root signature over `buildMoveReceiptMessage(...)`. */
|
|
99
|
-
signature: zod_1.z.string().trim().min(1).max(512),
|
|
100
|
-
})
|
|
101
|
-
.strict();
|
|
102
|
-
exports.identityMoveStateSchema = zod_1.z.object({
|
|
103
|
-
moveId: exports.identityMoveIdSchema,
|
|
104
|
-
status: zod_1.z.enum(exports.IDENTITY_MOVE_STATUSES),
|
|
105
|
-
initiatorCommitment: zod_1.z.string(),
|
|
106
|
-
initiatorCommitmentNonce: zod_1.z.string().nullable(),
|
|
107
|
-
publicKey: zod_1.z.string().regex(/^04[0-9a-f]{128}$/),
|
|
108
|
-
initiatorEphemeralPublicKey: exports.identityMoveEphemeralKeySchema.nullable(),
|
|
109
|
-
responderEphemeralPublicKey: exports.identityMoveEphemeralKeySchema.nullable(),
|
|
110
|
-
nonce: zod_1.z.string().nullable(),
|
|
111
|
-
ciphertext: zod_1.z.string().nullable(),
|
|
112
|
-
receiptSignature: zod_1.z.string().nullable(),
|
|
113
|
-
expiresAt: zod_1.z.string().datetime(),
|
|
114
|
-
});
|
|
115
|
-
/**
|
|
116
|
-
* The initiator's commitment input, `canonicalJson({ v, purpose, key, nonce })`,
|
|
117
|
-
* which the caller hashes with SHA-256.
|
|
118
|
-
*/
|
|
119
|
-
function buildMoveCommitmentInput(initiatorEphemeralPublicKey, nonce) {
|
|
120
|
-
return (0, identityProof_1.canonicalJson)({
|
|
121
|
-
v: 2,
|
|
122
|
-
purpose: 'oxy-identity-move-initiator-commitment',
|
|
123
|
-
initiatorEphemeralPublicKey: initiatorEphemeralPublicKey.toLowerCase(),
|
|
124
|
-
nonce: nonce.toLowerCase(),
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
/** The bytes both sides hash for the 6-digit code. */
|
|
128
|
-
function buildMoveSasInput(input) {
|
|
129
|
-
return (0, identityProof_1.canonicalJson)({
|
|
130
|
-
v: 'oxy-identity-transfer-sas-v2',
|
|
131
|
-
moveId: input.moveId.toLowerCase(),
|
|
132
|
-
initiator: input.initiatorEphemeralPublicKey.toLowerCase(),
|
|
133
|
-
responder: input.responderEphemeralPublicKey.toLowerCase(),
|
|
134
|
-
commitment: input.initiatorCommitment.toLowerCase(),
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
/** The ciphertext digest input the receipt binds: `canonicalJson({ nonce, ciphertext })`. */
|
|
138
|
-
function buildMoveCiphertextDigestInput(sealed) {
|
|
139
|
-
return (0, identityProof_1.canonicalJson)({ nonce: sealed.nonce.toLowerCase(), ciphertext: sealed.ciphertext.toLowerCase() });
|
|
140
|
-
}
|
|
141
|
-
/** The payload a seal proof digests. */
|
|
142
|
-
function buildMoveSealPayload(moveId, sealed) {
|
|
143
|
-
return { moveId: moveId.toLowerCase(), nonce: sealed.nonce.toLowerCase(), ciphertext: sealed.ciphertext.toLowerCase() };
|
|
144
|
-
}
|
|
145
|
-
/** The exact bytes a receipt signs. */
|
|
146
|
-
function buildMoveReceiptMessage(input) {
|
|
147
|
-
return (0, identityProof_1.canonicalJson)({
|
|
148
|
-
v: 2,
|
|
149
|
-
domain: 'oxy-identity-move-receipt',
|
|
150
|
-
moveId: input.moveId.toLowerCase(),
|
|
151
|
-
rootPublicKey: input.rootPublicKey.toLowerCase(),
|
|
152
|
-
initiator: input.initiatorEphemeralPublicKey.toLowerCase(),
|
|
153
|
-
responder: input.responderEphemeralPublicKey.toLowerCase(),
|
|
154
|
-
ciphertextDigest: input.ciphertextDigest.toLowerCase(),
|
|
155
|
-
});
|
|
156
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.identityRecoveryCompleteRequestSchema = exports.identityRecoveryStartRequestSchema = exports.identityRecoveryChallengeResponseSchema = exports.IDENTITY_RECOVERY_TTL_MS = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Signed-out recovery contract (ADR 0024 D5) — get an EXISTING account back from
|
|
6
|
-
* its root alone, with no passkey, session or email.
|
|
7
|
-
*
|
|
8
|
-
* 1. `POST /identity/recovery/challenge` → a one-use challenge. It says nothing
|
|
9
|
-
* about any account.
|
|
10
|
-
* 2. The holder derives the root locally from recovery material (12/24 words or a
|
|
11
|
-
* raw key) and signs `recover_account_start` over that challenge:
|
|
12
|
-
* `POST /identity/recovery/start { publicKey, proof }`. Only a valid proof
|
|
13
|
-
* learns which account the root belongs to; the response carries passkey
|
|
14
|
-
* registration options for THAT account and a short-lived ticket.
|
|
15
|
-
* 3. The holder creates the passkey, seals the root under it (PRF), and signs
|
|
16
|
-
* `recover_account_complete` over the registration challenge and the envelope
|
|
17
|
-
* digest: `POST /identity/recovery/complete`. The passkey, the envelope and a
|
|
18
|
-
* session are created in one transaction.
|
|
19
|
-
*
|
|
20
|
-
* The recovery material and the root never leave the holder. Platform-agnostic —
|
|
21
|
-
* zod only, ESM-safe.
|
|
22
|
-
*/
|
|
23
|
-
const zod_1 = require("zod");
|
|
24
|
-
const identityProof_1 = require("./identityProof");
|
|
25
|
-
const webIdentityCarrier_1 = require("./webIdentityCarrier");
|
|
26
|
-
/** A recovery attempt lives this long between steps. */
|
|
27
|
-
exports.IDENTITY_RECOVERY_TTL_MS = 5 * 60 * 1000;
|
|
28
|
-
exports.identityRecoveryChallengeResponseSchema = zod_1.z.object({
|
|
29
|
-
challenge: zod_1.z.string().regex(/^[0-9a-f]{64}$/),
|
|
30
|
-
expiresAt: zod_1.z.number().int().positive(),
|
|
31
|
-
});
|
|
32
|
-
exports.identityRecoveryStartRequestSchema = zod_1.z
|
|
33
|
-
.object({
|
|
34
|
-
publicKey: webIdentityCarrier_1.webIdentityPublicKeySchema,
|
|
35
|
-
/** `recover_account_start`, subject `root:<publicKey>`, actor `anonymous`. */
|
|
36
|
-
proof: identityProof_1.identityProofSchema,
|
|
37
|
-
})
|
|
38
|
-
.strict();
|
|
39
|
-
exports.identityRecoveryCompleteRequestSchema = zod_1.z
|
|
40
|
-
.object({
|
|
41
|
-
ticket: zod_1.z.string().regex(/^[0-9a-f]{64}$/),
|
|
42
|
-
/** The browser `RegistrationResponseJSON`, verified by the API's WebAuthn library. */
|
|
43
|
-
response: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
44
|
-
/** The root sealed under the new passkey — exactly one wrap, for that credential. */
|
|
45
|
-
envelope: webIdentityCarrier_1.webIdentityEnvelopeSchema,
|
|
46
|
-
/** `recover_account_complete`, subject = account id, actor `credential:<id>`, challenge = registration challenge (hex). */
|
|
47
|
-
proof: identityProof_1.identityProofSchema,
|
|
48
|
-
deviceName: zod_1.z.string().trim().max(100).optional(),
|
|
49
|
-
deviceFingerprint: zod_1.z.string().trim().max(512).optional(),
|
|
50
|
-
})
|
|
51
|
-
.strict();
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.webIdentityEnvelopeEstablishSchema = exports.webauthnAssertionResponseSchema = exports.webIdentityEnvelopePutSchema = exports.webIdentityEnvelopeActionSchema = exports.webIdentityEnvelopeProofFieldsSchema = exports.webIdentityEnvelopeResponseSchema = exports.webIdentityHolderSchema = exports.webIdentityEnvelopeUploadSchema = exports.webIdentityEnvelopeSchema = exports.webIdentityWrapSchema = exports.webauthnRpIdSchema = exports.webauthnCredentialIdSchema = exports.webIdentityPublicKeySchema = exports.WEB_IDENTITY_SECRET_KINDS = exports.WEB_IDENTITY_ENVELOPE_VERSION = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Web identity holder contract — the sealed envelope that lets a browser hold an
|
|
6
|
-
* account's self-custody root without Oxy ever being able to use it (ADR 0024).
|
|
7
|
-
*
|
|
8
|
-
* A root is a BIP-39 phrase (12–24 words) whose seed's first 32 bytes are the
|
|
9
|
-
* secp256k1 key — exactly the Commons derivation — or, for a few imported
|
|
10
|
-
* identities, a raw private key that never had a phrase. On the web it travels as:
|
|
11
|
-
*
|
|
12
|
-
* secret ── XChaCha20-Poly1305 under a random DEK ──▶ sealedSecret
|
|
13
|
-
* DEK ── XChaCha20-Poly1305 under KEK_i ──▶ wraps[i]
|
|
14
|
-
* KEK_i = HKDF(PRF output of passkey i)
|
|
15
|
-
*
|
|
16
|
-
* The server stores the envelope and can open NONE of it: the PRF output never
|
|
17
|
-
* leaves the user's authenticator, and the secret is never uploaded. The AEAD
|
|
18
|
-
* associated data binds the secret to the root's public key and kind, and each
|
|
19
|
-
* wrap to its credential and RP ID, so a re-labelled or transplanted envelope
|
|
20
|
-
* fails to open instead of decrypting into the wrong identity.
|
|
21
|
-
*
|
|
22
|
-
* Platform-agnostic — zod only, ESM-safe (no `require()`).
|
|
23
|
-
*/
|
|
24
|
-
const zod_1 = require("zod");
|
|
25
|
-
const identityProof_1 = require("./identityProof");
|
|
26
|
-
/** The envelope scheme. A scheme change is a new literal, never a mutation. */
|
|
27
|
-
exports.WEB_IDENTITY_ENVELOPE_VERSION = 2;
|
|
28
|
-
/**
|
|
29
|
-
* What an envelope seals. A raw-key identity stays a raw-key identity:
|
|
30
|
-
* nothing ever derives or displays a phrase for it.
|
|
31
|
-
*/
|
|
32
|
-
exports.WEB_IDENTITY_SECRET_KINDS = ['mnemonic-entropy', 'raw-private-key'];
|
|
33
|
-
const hex = (bytes, label) => zod_1.z
|
|
34
|
-
.string()
|
|
35
|
-
.trim()
|
|
36
|
-
.regex(new RegExp(`^[0-9a-fA-F]{${bytes * 2}}$`), `${label} must be ${bytes * 2} hex characters`);
|
|
37
|
-
/**
|
|
38
|
-
* The identity's secp256k1 public key in Oxy's canonical form: uncompressed SEC1
|
|
39
|
-
* (`04` + 64 bytes), lowercase hex — what `KeyManager.derivePublicKey` produces
|
|
40
|
-
* and `users.public_key` stores.
|
|
41
|
-
*/
|
|
42
|
-
exports.webIdentityPublicKeySchema = zod_1.z
|
|
43
|
-
.string()
|
|
44
|
-
.trim()
|
|
45
|
-
.regex(/^04[0-9a-f]{128}$/, 'publicKey must be an uncompressed, lowercase secp256k1 key (130 hex characters)');
|
|
46
|
-
/** A WebAuthn credential id, base64url as the browser reports it. */
|
|
47
|
-
exports.webauthnCredentialIdSchema = zod_1.z
|
|
48
|
-
.string()
|
|
49
|
-
.trim()
|
|
50
|
-
.min(16)
|
|
51
|
-
.max(1024)
|
|
52
|
-
.regex(/^[A-Za-z0-9_-]+$/, 'credentialId must be base64url');
|
|
53
|
-
/** A WebAuthn RP ID: a bare registrable host name, lowercase. */
|
|
54
|
-
exports.webauthnRpIdSchema = zod_1.z
|
|
55
|
-
.string()
|
|
56
|
-
.trim()
|
|
57
|
-
.min(1)
|
|
58
|
-
.max(253)
|
|
59
|
-
.regex(/^(localhost|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+)$/, 'rpId must be a lowercase host name');
|
|
60
|
-
/** One passkey's wrap of the envelope's data key. */
|
|
61
|
-
exports.webIdentityWrapSchema = zod_1.z.object({
|
|
62
|
-
credentialId: exports.webauthnCredentialIdSchema,
|
|
63
|
-
/** 24-byte XChaCha20-Poly1305 nonce. */
|
|
64
|
-
nonce: hex(24, 'nonce'),
|
|
65
|
-
/** The 32-byte DEK sealed under this passkey's KEK, with the 16-byte tag appended (48 bytes). */
|
|
66
|
-
wrappedKey: hex(48, 'wrappedKey'),
|
|
67
|
-
createdAt: zod_1.z.string().datetime(),
|
|
68
|
-
/** The RP ID the passkey was created under, asserted explicitly by every later ceremony (ADR 0024 D2). */
|
|
69
|
-
rpId: exports.webauthnRpIdSchema,
|
|
70
|
-
/**
|
|
71
|
-
* When this passkey's PRF output was shown to open the envelope. A wrap is a
|
|
72
|
-
* root HOLDER only once this is set; a login passkey never is by default.
|
|
73
|
-
*/
|
|
74
|
-
verifiedAt: zod_1.z.string().datetime().optional(),
|
|
75
|
-
});
|
|
76
|
-
/**
|
|
77
|
-
* The sealed identity as it is stored (server copy and local copy alike).
|
|
78
|
-
*
|
|
79
|
-
* `wraps` holds one entry per passkey able to open it; at least one, and a
|
|
80
|
-
* bounded number so an envelope cannot grow without limit.
|
|
81
|
-
*/
|
|
82
|
-
exports.webIdentityEnvelopeSchema = zod_1.z
|
|
83
|
-
.object({
|
|
84
|
-
version: zod_1.z.literal(exports.WEB_IDENTITY_ENVELOPE_VERSION),
|
|
85
|
-
algorithm: zod_1.z.literal('xchacha20poly1305'),
|
|
86
|
-
publicKey: exports.webIdentityPublicKeySchema,
|
|
87
|
-
secretKind: zod_1.z.enum(exports.WEB_IDENTITY_SECRET_KINDS),
|
|
88
|
-
/** 24-byte nonce of the secret seal. */
|
|
89
|
-
secretNonce: hex(24, 'secretNonce'),
|
|
90
|
-
/**
|
|
91
|
-
* The sealed secret, tag appended: 16/20/24/28/32 bytes of BIP-39 entropy
|
|
92
|
-
* (12–24 words) or a 32-byte private key, plus 16.
|
|
93
|
-
*/
|
|
94
|
-
sealedSecret: zod_1.z
|
|
95
|
-
.string()
|
|
96
|
-
.trim()
|
|
97
|
-
.regex(/^(?:[0-9a-fA-F]{64}|[0-9a-fA-F]{72}|[0-9a-fA-F]{80}|[0-9a-fA-F]{88}|[0-9a-fA-F]{96})$/, 'sealedSecret has an unsupported length'),
|
|
98
|
-
wraps: zod_1.z.array(exports.webIdentityWrapSchema).min(1).max(10),
|
|
99
|
-
})
|
|
100
|
-
.refine((envelope) => envelope.secretKind === 'mnemonic-entropy' || envelope.sealedSecret.length === 96, {
|
|
101
|
-
message: 'a raw private key seals to 48 bytes',
|
|
102
|
-
path: ['sealedSecret'],
|
|
103
|
-
});
|
|
104
|
-
/**
|
|
105
|
-
* `PUT /identity/web-envelope` — store or replace the caller's envelope.
|
|
106
|
-
*
|
|
107
|
-
* Refused unless `envelope.publicKey` is the identity key already linked to the
|
|
108
|
-
* account: an envelope can only ever carry the account's own identity.
|
|
109
|
-
*/
|
|
110
|
-
exports.webIdentityEnvelopeUploadSchema = zod_1.z.object({
|
|
111
|
-
envelope: exports.webIdentityEnvelopeSchema,
|
|
112
|
-
});
|
|
113
|
-
/** A root holder as the status read reports it — metadata only, nothing that opens anything. */
|
|
114
|
-
exports.webIdentityHolderSchema = zod_1.z.object({
|
|
115
|
-
credentialId: exports.webauthnCredentialIdSchema,
|
|
116
|
-
rpId: exports.webauthnRpIdSchema,
|
|
117
|
-
verifiedAt: zod_1.z.string().datetime().nullable(),
|
|
118
|
-
createdAt: zod_1.z.string().datetime(),
|
|
119
|
-
});
|
|
120
|
-
/**
|
|
121
|
-
* `GET /identity/web-envelope` — the caller's envelope and the readiness facts
|
|
122
|
-
* ADR 0024 D5 keeps separate. A client decides what to show from these fields
|
|
123
|
-
* WITHOUT decrypting anything.
|
|
124
|
-
*/
|
|
125
|
-
exports.webIdentityEnvelopeResponseSchema = zod_1.z.object({
|
|
126
|
-
envelope: exports.webIdentityEnvelopeSchema.nullable(),
|
|
127
|
-
/** The revision a write must name as `expectedRevision`; `0` when there is no envelope. */
|
|
128
|
-
revision: zod_1.z.number().int().nonnegative(),
|
|
129
|
-
/** Whether the account has a linked root at all (it may live only in Commons). */
|
|
130
|
-
rootLinked: zod_1.z.boolean(),
|
|
131
|
-
/** The web wraps, as metadata. */
|
|
132
|
-
holders: zod_1.z.array(exports.webIdentityHolderSchema),
|
|
133
|
-
/** When the owner confirmed the recovery material is written down, or `null`. */
|
|
134
|
-
phraseConfirmedAt: zod_1.z.string().datetime().nullable(),
|
|
135
|
-
/** When the recovery material was shown to re-derive this root, or `null`. */
|
|
136
|
-
recoveryVerifiedAt: zod_1.z.string().datetime().nullable(),
|
|
137
|
-
updatedAt: zod_1.z.string().datetime().nullable(),
|
|
138
|
-
});
|
|
139
|
-
/** A root proof, plus the envelope revision the write expects to replace. */
|
|
140
|
-
exports.webIdentityEnvelopeProofFieldsSchema = zod_1.z.object({
|
|
141
|
-
proof: identityProof_1.identityProofSchema,
|
|
142
|
-
expectedRevision: zod_1.z.number().int().nonnegative(),
|
|
143
|
-
});
|
|
144
|
-
/**
|
|
145
|
-
* `POST /identity/web-envelope/phrase-confirmed`, `/recovery-verified` and
|
|
146
|
-
* `DELETE /identity/web-envelope` prove control of the root, not just a bearer.
|
|
147
|
-
*/
|
|
148
|
-
exports.webIdentityEnvelopeActionSchema = exports.webIdentityEnvelopeProofFieldsSchema.strict();
|
|
149
|
-
/** `PUT /identity/web-envelope` body. */
|
|
150
|
-
exports.webIdentityEnvelopePutSchema = exports.webIdentityEnvelopeUploadSchema.extend(exports.webIdentityEnvelopeProofFieldsSchema.shape).strict();
|
|
151
|
-
/**
|
|
152
|
-
* A WebAuthn assertion by one of the account's EXISTING passkeys whose
|
|
153
|
-
* `clientDataJSON.challenge` is the proof challenge — the fresh use of the
|
|
154
|
-
* existing factor a keyless account needs before its first root is linked.
|
|
155
|
-
*/
|
|
156
|
-
exports.webauthnAssertionResponseSchema = zod_1.z
|
|
157
|
-
.object({
|
|
158
|
-
id: exports.webauthnCredentialIdSchema,
|
|
159
|
-
rawId: zod_1.z.string().min(1).max(2048),
|
|
160
|
-
type: zod_1.z.literal('public-key'),
|
|
161
|
-
response: zod_1.z
|
|
162
|
-
.object({
|
|
163
|
-
clientDataJSON: zod_1.z.string().min(1).max(8192),
|
|
164
|
-
authenticatorData: zod_1.z.string().min(1).max(8192),
|
|
165
|
-
signature: zod_1.z.string().min(1).max(2048),
|
|
166
|
-
userHandle: zod_1.z.string().max(2048).optional(),
|
|
167
|
-
})
|
|
168
|
-
.passthrough(),
|
|
169
|
-
clientExtensionResults: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
170
|
-
authenticatorAttachment: zod_1.z.string().optional(),
|
|
171
|
-
})
|
|
172
|
-
.passthrough();
|
|
173
|
-
/**
|
|
174
|
-
* `POST /identity/web-envelope/establish` body — an account's FIRST root, linked
|
|
175
|
-
* and stored with its envelope in ONE transaction: one root proof
|
|
176
|
-
* (`web_envelope_establish`, digest of the envelope) plus a fresh `assertion` by
|
|
177
|
-
* an existing passkey over the same challenge.
|
|
178
|
-
*/
|
|
179
|
-
exports.webIdentityEnvelopeEstablishSchema = exports.webIdentityEnvelopeUploadSchema
|
|
180
|
-
.extend({ proof: identityProof_1.identityProofSchema, assertion: exports.webauthnAssertionResponseSchema })
|
|
181
|
-
.strict();
|
package/dist/cjs/webauthn.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* WebAuthn / passkey ceremony contracts (Fase B/b1).
|
|
4
|
-
*
|
|
5
|
-
* These schemas describe ONLY the outer Oxy envelope that wraps a WebAuthn
|
|
6
|
-
* ceremony request — the username the client is registering/authenticating as,
|
|
7
|
-
* plus non-authoritative device metadata. The browser
|
|
8
|
-
* `RegistrationResponseJSON` / `AuthenticationResponseJSON` payloads are NOT
|
|
9
|
-
* mirrored here: they are validated by `@simplewebauthn/server` inside the route
|
|
10
|
-
* (`verifyRegistrationResponse` / `verifyAuthenticationResponse`), which is the
|
|
11
|
-
* single source of truth for their structure. Re-encoding them in Zod would just
|
|
12
|
-
* create a second, drift-prone definition of a shape we do not own.
|
|
13
|
-
*/
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.webauthnLoginVerifyRequestSchema = exports.webauthnRegisterVerifyRequestSchema = exports.webauthnLoginOptionsRequestSchema = exports.webauthnRegisterOptionsRequestSchema = void 0;
|
|
16
|
-
const zod_1 = require("zod");
|
|
17
|
-
const identityProof_1 = require("./identityProof");
|
|
18
|
-
const webIdentityCarrier_1 = require("./webIdentityCarrier");
|
|
19
|
-
/**
|
|
20
|
-
* Device-session options shared by every first-party sign-in body
|
|
21
|
-
* (`deviceName`/`deviceFingerprint`). Mirrors what
|
|
22
|
-
* `sessionCreateOptionsFromBody` reads in the API so a WebAuthn login/verify can
|
|
23
|
-
* name its resulting session exactly like `/auth/login` or `/auth/verify`.
|
|
24
|
-
*
|
|
25
|
-
* There is deliberately no `deviceId` here. `createSession` treats an explicit
|
|
26
|
-
* device id as authoritative — `deviceId > stableDeviceKey > UA/IP > random` —
|
|
27
|
-
* and stamps it verbatim, so on an UNAUTHENTICATED sign-in body it is a caller
|
|
28
|
-
* naming somebody else's device and having a session, and then a device secret,
|
|
29
|
-
* minted against it. The legitimate callers that pin a device id are
|
|
30
|
-
* server-side and already authorized (the account-switch route threading the
|
|
31
|
-
* operator's own central device id); they pass it to `createSession` directly
|
|
32
|
-
* and never through a request body.
|
|
33
|
-
*/
|
|
34
|
-
const deviceSessionEnvelope = {
|
|
35
|
-
deviceName: zod_1.z.string().trim().min(1).max(120).optional(),
|
|
36
|
-
deviceFingerprint: zod_1.z.string().trim().min(1).max(256).optional(),
|
|
37
|
-
};
|
|
38
|
-
/**
|
|
39
|
-
* `POST /webauthn/register/options` — request registration options. With a bearer
|
|
40
|
-
* token the caller links a passkey to their signed-in account and `username` is
|
|
41
|
-
* ignored; without one it is a prospective signup and `username` is the desired
|
|
42
|
-
* (not-yet-created) handle.
|
|
43
|
-
*/
|
|
44
|
-
exports.webauthnRegisterOptionsRequestSchema = zod_1.z.object({
|
|
45
|
-
username: zod_1.z.string().trim().min(1).max(60).optional(),
|
|
46
|
-
});
|
|
47
|
-
/**
|
|
48
|
-
* `POST /webauthn/login/options` — request authentication options. When
|
|
49
|
-
* `username` is present the server scopes `allowCredentials` to that user's
|
|
50
|
-
* passkeys (username-first); when omitted it returns an empty allow-list for the
|
|
51
|
-
* usernameless / discoverable-credential flow (the default).
|
|
52
|
-
*/
|
|
53
|
-
exports.webauthnLoginOptionsRequestSchema = zod_1.z.object({
|
|
54
|
-
username: zod_1.z.string().trim().min(1).max(60).optional(),
|
|
55
|
-
});
|
|
56
|
-
/**
|
|
57
|
-
* `POST /webauthn/register/verify` — the outer envelope. The browser
|
|
58
|
-
* `RegistrationResponseJSON` travels alongside these fields under `response` and
|
|
59
|
-
* is validated by `@simplewebauthn/server`, not here. `username` is required only
|
|
60
|
-
* for the prospective-signup branch (no bearer); the linking branch ignores it.
|
|
61
|
-
*/
|
|
62
|
-
exports.webauthnRegisterVerifyRequestSchema = zod_1.z.object({
|
|
63
|
-
username: zod_1.z.string().trim().min(1).max(60).optional(),
|
|
64
|
-
/**
|
|
65
|
-
* Sign-up only (ADR 0024 D4): the account's root, created on the holder BEFORE
|
|
66
|
-
* this request — sealed under the passkey being registered — and a root proof
|
|
67
|
-
* (`enroll_identity`) whose challenge is the registration challenge. The
|
|
68
|
-
* account, passkey, root and envelope are then created in one transaction.
|
|
69
|
-
*/
|
|
70
|
-
identity: zod_1.z
|
|
71
|
-
.object({
|
|
72
|
-
envelope: webIdentityCarrier_1.webIdentityEnvelopeSchema,
|
|
73
|
-
proof: identityProof_1.identityProofSchema,
|
|
74
|
-
})
|
|
75
|
-
.optional(),
|
|
76
|
-
...deviceSessionEnvelope,
|
|
77
|
-
});
|
|
78
|
-
/**
|
|
79
|
-
* `POST /webauthn/login/verify` — the outer envelope. The browser
|
|
80
|
-
* `AuthenticationResponseJSON` travels alongside these fields under `response`
|
|
81
|
-
* and is validated by `@simplewebauthn/server`, not here.
|
|
82
|
-
*/
|
|
83
|
-
exports.webauthnLoginVerifyRequestSchema = zod_1.z.object({
|
|
84
|
-
...deviceSessionEnvelope,
|
|
85
|
-
});
|
package/dist/esm/identityMove.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Identity move contract — give a web root to Commons (add it, or keep it only in
|
|
3
|
-
* Commons; ADR 0024 D6).
|
|
4
|
-
*
|
|
5
|
-
* The web (holding the root) shows a QR carrying only a move id; Commons scans.
|
|
6
|
-
* The relay in between must not be able to take the root, so:
|
|
7
|
-
*
|
|
8
|
-
* 1. `POST /identity/move { initiatorCommitment }` — the web publishes only
|
|
9
|
-
* `H(initiatorKey, nonce)`, never the key.
|
|
10
|
-
* 2. `POST /identity/move/:moveId/join { responderEphemeralPublicKey }` —
|
|
11
|
-
* Commons, having read the commitment first, joins with its own key.
|
|
12
|
-
* 3. `POST /identity/move/:moveId/reveal` — only then does the web reveal its
|
|
13
|
-
* key and nonce; Commons checks them against the commitment it read.
|
|
14
|
-
* 4. Both screens show a 6-digit code over `(moveId, both keys, commitment)`;
|
|
15
|
-
* the person confirms on the web that they match.
|
|
16
|
-
* 5. `POST /identity/move/:moveId/seal` — the web seals the phrase entropy under
|
|
17
|
-
* `HKDF(ECDH(both keys), moveId)`, authorized by a one-use root proof over the
|
|
18
|
-
* exact sealed bytes.
|
|
19
|
-
* 6. `POST /identity/move/:moveId/receipt` — Commons stores the root, reads it
|
|
20
|
-
* back from its keychain, and signs a receipt over the move, the root, both
|
|
21
|
-
* keys and the digest of the ciphertext it opened. The server verifies it; the
|
|
22
|
-
* web verifies it again from what IT sealed before removing anything.
|
|
23
|
-
*
|
|
24
|
-
* Why the commitment: without it the relay sees both keys before committing to
|
|
25
|
-
* anything and can grind a substituted key until the two codes agree (~10⁶
|
|
26
|
-
* tries, seconds). With it, the relay must fix the key it shows Commons before
|
|
27
|
-
* learning Commons' key, and the key it shows the web before the web reveals.
|
|
28
|
-
*
|
|
29
|
-
* The server holds a commitment, two ephemeral public keys, an opaque ciphertext
|
|
30
|
-
* and a receipt — nothing that decrypts the ciphertext.
|
|
31
|
-
*
|
|
32
|
-
* Platform-agnostic — zod only, ESM-safe (no `require()`).
|
|
33
|
-
*/
|
|
34
|
-
import { z } from 'zod';
|
|
35
|
-
import { canonicalJson, identityProofSchema } from './identityProof.js';
|
|
36
|
-
/** A move lives this long: one interactive handoff. */
|
|
37
|
-
export const IDENTITY_MOVE_TTL_MS = 5 * 60 * 1000;
|
|
38
|
-
/** 128-bit move id, lowercase hex. */
|
|
39
|
-
export const identityMoveIdSchema = z
|
|
40
|
-
.string()
|
|
41
|
-
.trim()
|
|
42
|
-
.regex(/^[0-9a-f]{32}$/, 'moveId must be 32 lowercase hex characters');
|
|
43
|
-
/** An ephemeral secp256k1 public key, uncompressed lowercase hex. */
|
|
44
|
-
export const identityMoveEphemeralKeySchema = z
|
|
45
|
-
.string()
|
|
46
|
-
.trim()
|
|
47
|
-
.regex(/^04[0-9a-f]{128}$/, 'ephemeral public key must be uncompressed, lowercase hex');
|
|
48
|
-
export const IDENTITY_MOVE_STATUSES = ['pending', 'joined', 'sealed', 'completed', 'cancelled', 'expired'];
|
|
49
|
-
/** The QR payload Commons scans. Carries the move id only. */
|
|
50
|
-
export const IDENTITY_MOVE_QR_PREFIX = 'oxycommons://move?id=';
|
|
51
|
-
const hex64 = (label) => z.string().trim().regex(/^[0-9a-f]{64}$/, `${label} must be 64 lowercase hex characters`);
|
|
52
|
-
export const identityMoveCreateRequestSchema = z
|
|
53
|
-
.object({
|
|
54
|
-
/** `H(initiator ephemeral key, nonce)` — the key itself is revealed only after Commons joins. */
|
|
55
|
-
initiatorCommitment: hex64('initiatorCommitment'),
|
|
56
|
-
})
|
|
57
|
-
.strict();
|
|
58
|
-
export const identityMoveCreateResponseSchema = z.object({
|
|
59
|
-
moveId: identityMoveIdSchema,
|
|
60
|
-
expiresAt: z.string().datetime(),
|
|
61
|
-
});
|
|
62
|
-
export const identityMoveJoinRequestSchema = z.object({
|
|
63
|
-
responderEphemeralPublicKey: identityMoveEphemeralKeySchema,
|
|
64
|
-
});
|
|
65
|
-
/** After Commons joined: the committed key and its nonce. */
|
|
66
|
-
export const identityMoveRevealRequestSchema = z
|
|
67
|
-
.object({
|
|
68
|
-
initiatorEphemeralPublicKey: identityMoveEphemeralKeySchema,
|
|
69
|
-
commitmentNonce: hex64('commitmentNonce'),
|
|
70
|
-
})
|
|
71
|
-
.strict();
|
|
72
|
-
export const identityMoveSealRequestSchema = z
|
|
73
|
-
.object({
|
|
74
|
-
/** 24-byte XChaCha20-Poly1305 nonce, hex. */
|
|
75
|
-
nonce: z.string().trim().regex(/^[0-9a-f]{48}$/, 'nonce must be 48 lowercase hex characters'),
|
|
76
|
-
/** The 16–32-byte phrase entropy (12–24 words), tag appended, hex. */
|
|
77
|
-
ciphertext: z
|
|
78
|
-
.string()
|
|
79
|
-
.trim()
|
|
80
|
-
.regex(/^(?:[0-9a-f]{64}|[0-9a-f]{72}|[0-9a-f]{80}|[0-9a-f]{88}|[0-9a-f]{96})$/, 'ciphertext has an unsupported length'),
|
|
81
|
-
/**
|
|
82
|
-
* Root proof (`identity_move_seal`), payload `{ moveId, nonce, ciphertext }`,
|
|
83
|
-
* over a one-use challenge from `POST /identity/proof-challenge`.
|
|
84
|
-
*/
|
|
85
|
-
proof: identityProofSchema,
|
|
86
|
-
})
|
|
87
|
-
.strict();
|
|
88
|
-
export const identityMoveReceiptRequestSchema = z
|
|
89
|
-
.object({
|
|
90
|
-
/** Root signature over `buildMoveReceiptMessage(...)`. */
|
|
91
|
-
signature: z.string().trim().min(1).max(512),
|
|
92
|
-
})
|
|
93
|
-
.strict();
|
|
94
|
-
export const identityMoveStateSchema = z.object({
|
|
95
|
-
moveId: identityMoveIdSchema,
|
|
96
|
-
status: z.enum(IDENTITY_MOVE_STATUSES),
|
|
97
|
-
initiatorCommitment: z.string(),
|
|
98
|
-
initiatorCommitmentNonce: z.string().nullable(),
|
|
99
|
-
publicKey: z.string().regex(/^04[0-9a-f]{128}$/),
|
|
100
|
-
initiatorEphemeralPublicKey: identityMoveEphemeralKeySchema.nullable(),
|
|
101
|
-
responderEphemeralPublicKey: identityMoveEphemeralKeySchema.nullable(),
|
|
102
|
-
nonce: z.string().nullable(),
|
|
103
|
-
ciphertext: z.string().nullable(),
|
|
104
|
-
receiptSignature: z.string().nullable(),
|
|
105
|
-
expiresAt: z.string().datetime(),
|
|
106
|
-
});
|
|
107
|
-
/**
|
|
108
|
-
* The initiator's commitment input, `canonicalJson({ v, purpose, key, nonce })`,
|
|
109
|
-
* which the caller hashes with SHA-256.
|
|
110
|
-
*/
|
|
111
|
-
export function buildMoveCommitmentInput(initiatorEphemeralPublicKey, nonce) {
|
|
112
|
-
return canonicalJson({
|
|
113
|
-
v: 2,
|
|
114
|
-
purpose: 'oxy-identity-move-initiator-commitment',
|
|
115
|
-
initiatorEphemeralPublicKey: initiatorEphemeralPublicKey.toLowerCase(),
|
|
116
|
-
nonce: nonce.toLowerCase(),
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
/** The bytes both sides hash for the 6-digit code. */
|
|
120
|
-
export function buildMoveSasInput(input) {
|
|
121
|
-
return canonicalJson({
|
|
122
|
-
v: 'oxy-identity-transfer-sas-v2',
|
|
123
|
-
moveId: input.moveId.toLowerCase(),
|
|
124
|
-
initiator: input.initiatorEphemeralPublicKey.toLowerCase(),
|
|
125
|
-
responder: input.responderEphemeralPublicKey.toLowerCase(),
|
|
126
|
-
commitment: input.initiatorCommitment.toLowerCase(),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
/** The ciphertext digest input the receipt binds: `canonicalJson({ nonce, ciphertext })`. */
|
|
130
|
-
export function buildMoveCiphertextDigestInput(sealed) {
|
|
131
|
-
return canonicalJson({ nonce: sealed.nonce.toLowerCase(), ciphertext: sealed.ciphertext.toLowerCase() });
|
|
132
|
-
}
|
|
133
|
-
/** The payload a seal proof digests. */
|
|
134
|
-
export function buildMoveSealPayload(moveId, sealed) {
|
|
135
|
-
return { moveId: moveId.toLowerCase(), nonce: sealed.nonce.toLowerCase(), ciphertext: sealed.ciphertext.toLowerCase() };
|
|
136
|
-
}
|
|
137
|
-
/** The exact bytes a receipt signs. */
|
|
138
|
-
export function buildMoveReceiptMessage(input) {
|
|
139
|
-
return canonicalJson({
|
|
140
|
-
v: 2,
|
|
141
|
-
domain: 'oxy-identity-move-receipt',
|
|
142
|
-
moveId: input.moveId.toLowerCase(),
|
|
143
|
-
rootPublicKey: input.rootPublicKey.toLowerCase(),
|
|
144
|
-
initiator: input.initiatorEphemeralPublicKey.toLowerCase(),
|
|
145
|
-
responder: input.responderEphemeralPublicKey.toLowerCase(),
|
|
146
|
-
ciphertextDigest: input.ciphertextDigest.toLowerCase(),
|
|
147
|
-
});
|
|
148
|
-
}
|