Skip to content

Format-preserving encryption capabilities #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 7, 2025
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@
"@crossmint/client-sdk-window": "1.0.0",
"@crossmint/client-signers": "0.0.8",
"@hpke/core": "^1.7.2",
"shamir-secret-sharing": "^0.0.4",
"@noble/ciphers": "^1.3.0",
"bs58": "^6.0.0",
"shamir-secret-sharing": "^0.0.4",
"zod": "3.22.4"
}
}
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 15 additions & 3 deletions src/services/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ describe('CrossmintApiService', () => {
const authData = { jwt: 'test-jwt', apiKey: 'test-api-key' };

it('should properly call createSigner with correct parameters', async () => {
const data = { authId: 'test-auth-id', chainLayer: 'solana' };
const data = {
authId: 'test-auth-id',
chainLayer: 'solana',
encryptionContext: {
publicKey: 'test-public-key',
},
};
executeSpy.mockResolvedValueOnce({ success: true });

await apiService.createSigner(deviceId, data, authData);
Expand All @@ -78,19 +84,25 @@ describe('CrossmintApiService', () => {
expect.objectContaining({
authId: 'test-auth-id',
chainLayer: 'solana',
encryptionContext: {
publicKey: 'test-public-key',
},
}),
authData
);
});

it('should properly call sendOtp with correct parameters and return shares', async () => {
const data = { otp: '123456' };
const data = { otp: '123456', publicKey: 'test-public-key' };
const mockResponse = { shares: { device: 'device-share', auth: 'auth-share' } };
executeSpy.mockResolvedValueOnce(mockResponse);

const result = await apiService.sendOtp(deviceId, data, authData);

expect(executeSpy).toHaveBeenCalledWith(expect.objectContaining({ otp: '123456' }), authData);
expect(executeSpy).toHaveBeenCalledWith(
expect.objectContaining({ otp: '123456', publicKey: 'test-public-key' }),
authData
);
expect(result).toEqual(mockResponse);
});
});
Expand Down
9 changes: 7 additions & 2 deletions src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,15 @@ export class CrossmintApiService extends XMIFService {
async init() {}

// Zod schemas
static createSignerInputSchema = z.object({ authId: z.string() });
static createSignerInputSchema = z.object({
authId: z.string(),
encryptionContext: z.object({
publicKey: z.string(),
}),
});
static createSignerOutputSchema = z.object({});

static sendOtpInputSchema = z.object({ otp: z.string() });
static sendOtpInputSchema = z.object({ otp: z.string(), publicKey: z.string() });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We add the public key for the TEE to derive the corresponding AES256 encryption key

static sendOtpOutputSchema = z.object({
shares: z.object({
device: z.string(),
Expand Down
37 changes: 37 additions & 0 deletions src/services/encryption-consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { z } from 'zod';

export type EncryptionResult<T extends ArrayBuffer | string> = {
ciphertext: T;
encapsulatedKey: T;
publicKey: T;
};
export type DecryptOptions = {
validateTeeSender: boolean;
};

export const SerializedPublicKeySchema = z.object({
raw: z.string(),
algorithm: z.any(),
});
export type SerializedPublicKey = z.infer<typeof SerializedPublicKeySchema>;

export const SerializedPrivateKeySchema = z.object({
raw: z.any(),
usages: z.array(z.custom<KeyUsage>()),
algorithm: z.any(),
});
export type SerializedPrivateKey = z.infer<typeof SerializedPrivateKeySchema>;

export const AES256_KEY_SPEC: AesKeyGenParams = {
name: 'AES-GCM' as const,
length: 256,
} as const;
export const ECDH_KEY_SPEC: EcKeyGenParams = {
name: 'ECDH' as const,
namedCurve: 'P-384' as const,
} as const;

export const STORAGE_KEYS = {
PRIV_KEY: 'private-key',
PUB_KEY: 'public-key',
} as const;
Loading
Loading