Initial Commit

This commit is contained in:
2026-06-08 13:28:20 +00:00
commit af31aac19c
13 changed files with 2572 additions and 0 deletions

124
src/utils/ext-json.ts Normal file
View File

@@ -0,0 +1,124 @@
/**
* TODO: These are intended as temporary stand-ins until this functionality has been implemented directly in LibAuth.
* We are doing this so that we may better standardize with the rest of the BCH eco-system in future.
* See: https://github.com/bitauth/libauth/pull/108
*/
import { binToHex, hexToBin } from '@bitauth/libauth';
export const extendedJsonReplacer = function (value: any): any {
if (typeof value === 'bigint') {
return `<bigint: ${value.toString()}n>`;
} else if (value instanceof Uint8Array) {
return `<Uint8Array: ${binToHex(value)}>`;
}
return value;
};
export const extendedJsonReviver = function (value: any): any {
// Define RegEx that matches our Extended JSON fields.
const bigIntRegex = /^<bigint: (?<bigint>[+-]?[0-9]*)n>$/;
const uint8ArrayRegex = /^<Uint8Array: (?<hex>[a-f0-9]*)>$/;
// Only perform a check if the value is a string.
// NOTE: We can skip all other values as all Extended JSON encoded fields WILL be a string.
if (typeof value === 'string') {
// Check if this value matches an Extended JSON encoded bigint.
const bigintMatch = value.match(bigIntRegex);
if (bigintMatch) {
// Access the named group directly instead of using array indices
const { bigint } = bigintMatch.groups!;
// Return the value casted to bigint.
return BigInt(bigint!);
}
const uint8ArrayMatch = value.match(uint8ArrayRegex);
if (uint8ArrayMatch) {
// Access the named group directly instead of using array indices
const { hex } = uint8ArrayMatch.groups!;
// Return the value casted to bigint.
return hexToBin(hex!);
}
}
// Return the original value.
return value;
};
export const encodeExtendedJsonObject = function (value: any): any {
// If this is an object type (and it is not null - which is technically an "object")...
// ... and it is not an ArrayBuffer (e.g. Uint8Array) which is also technically an "object...
if (
typeof value === 'object' &&
value !== null &&
!ArrayBuffer.isView(value)
) {
// If this is an array, recursively call this function on each value.
if (Array.isArray(value)) {
return value.map(encodeExtendedJsonObject);
}
// Declare object to store extended JSON entries.
const encodedObject: any = {};
// Iterate through each entry and encode it to extended JSON.
for (const [key, valueToEncode] of Object.entries(value)) {
encodedObject[key] = encodeExtendedJsonObject(valueToEncode);
}
// Return the extended JSON encoded object.
return encodedObject;
}
// Return the replaced value.
return extendedJsonReplacer(value);
};
export const decodeExtendedJsonObject = function (value: any): any {
// If this is an object type (and it is not null - which is technically an "object")...
// ... and it is not an ArrayBuffer (e.g. Uint8Array) which is also technically an "object...
if (
typeof value === 'object' &&
value !== null &&
!ArrayBuffer.isView(value)
) {
// If this is an array, recursively call this function on each value.
if (Array.isArray(value)) {
return value.map(decodeExtendedJsonObject);
}
// Declare object to store decoded JSON entries.
const decodedObject: any = {};
// Iterate through each entry and decode it from extended JSON.
for (const [key, valueToEncode] of Object.entries(value)) {
decodedObject[key] = decodeExtendedJsonObject(valueToEncode);
}
// Return the extended JSON encoded object.
return decodedObject;
}
// Return the revived value.
return extendedJsonReviver(value);
};
export const encodeExtendedJson = function (
value: any,
space: number | undefined = undefined,
): string {
const replacedObject = encodeExtendedJsonObject(value);
const stringifiedObject = JSON.stringify(replacedObject, null, space);
return stringifiedObject;
};
export const decodeExtendedJson = function (json: string): any {
const parsedObject = JSON.parse(json);
const revivedObject = decodeExtendedJsonObject(parsedObject);
return revivedObject;
};

View File

@@ -0,0 +1,68 @@
import Z, { z } from 'zod';
/**
* Zod schemas for invitation validation.
*
* IMPORTANT: We use .passthrough() on all object schemas to preserve fields
* that aren't explicitly defined. This is critical because:
* 1. Invitations are signed based on stringify(commit.data)
* 2. If we strip fields, the signature verification will fail
* 3. The actual XOInvitation types have many more fields than we validate here
*/
const variableSchema = z.object({
variableIdentifier: z.string(),
roleIdentifier: z.string().optional(),
value: z.number().or(z.string()).or(z.boolean()).or(z.bigint()),
}).passthrough();
const mergesWithSchema = z.object({
commitIdentifier: z.string(),
index: z.number(),
}).passthrough();
const inputSchema = z.object({
inputIdentifier: z.string().optional(),
transactionIndex: z.number().optional(),
roleIdentifier: z.string().optional(),
mergesWith: mergesWithSchema.optional(),
// Additional fields preserved via passthrough:
// outpointTransactionHash, outpointIndex, sequenceNumber, unlockingBytecode, etc.
}).passthrough();
const outputSchema = z.object({
outputIdentifier: z.string().optional(),
roleIdentifier: z.string().optional(),
secretIdentifier: z.string().optional(),
transactionIndex: z.number().optional(),
mergesWith: mergesWithSchema.optional(),
// Additional fields preserved via passthrough:
// valueSatoshis, lockingBytecode, token, etc.
}).passthrough();
const dataSchema = z.object({
transactionVersion: z.number().optional(),
transactionLocktime: z.number().optional(),
variables: z.array(variableSchema).optional(),
inputs: z.array(inputSchema).optional(),
outputs: z.array(outputSchema).optional(),
}).passthrough();
const commitSchema = z.object({
commitIdentifier: z.string(),
previousCommitIdentifier: z.string().or(z.undefined()),
entityIdentifier: z.string(),
data: dataSchema,
signature: z.string(),
expiresAtTimestamp: z.number(),
}).passthrough();
export const parseInvitation = z.object({
invitationIdentifier: z.string(),
commits: z.array(commitSchema),
createdAtTimestamp: z.number(),
templateIdentifier: z.string(),
actionIdentifier: z.string(),
}).passthrough();
export type InvitationSchema = Z.infer<typeof parseInvitation>;