@yallet/rwa-sdk 0.2.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/README.md +365 -0
- package/dist/credential-nft-server.d.ts +75 -0
- package/dist/credential-nft-server.d.ts.map +1 -0
- package/dist/credential-nft-server.js +122 -0
- package/dist/credential-nft-server.js.map +1 -0
- package/dist/credential-nft.d.ts +31 -0
- package/dist/credential-nft.d.ts.map +1 -0
- package/dist/credential-nft.js +38 -0
- package/dist/credential-nft.js.map +1 -0
- package/dist/encryption.d.ts +107 -0
- package/dist/encryption.d.ts.map +1 -0
- package/dist/encryption.js +176 -0
- package/dist/encryption.js.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata.d.ts +161 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +180 -0
- package/dist/metadata.js.map +1 -0
- package/dist/minting.d.ts +63 -0
- package/dist/minting.d.ts.map +1 -0
- package/dist/minting.js +194 -0
- package/dist/minting.js.map +1 -0
- package/dist/payloads.d.ts +120 -0
- package/dist/payloads.d.ts.map +1 -0
- package/dist/payloads.js +118 -0
- package/dist/payloads.js.map +1 -0
- package/dist/registry.d.ts +77 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +173 -0
- package/dist/registry.js.map +1 -0
- package/dist/sdk.d.ts +158 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +346 -0
- package/dist/sdk.js.map +1 -0
- package/dist/storage.d.ts +80 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +284 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +278 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
- package/src/credential-nft-server.ts +201 -0
- package/src/credential-nft.ts +64 -0
- package/src/encryption.ts +308 -0
- package/src/index.ts +151 -0
- package/src/metadata.ts +336 -0
- package/src/minting.ts +266 -0
- package/src/payloads.ts +238 -0
- package/src/registry.ts +236 -0
- package/src/sdk.ts +507 -0
- package/src/storage.ts +364 -0
- package/src/types.ts +318 -0
- package/wasm/README.md +33 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yault credential NFT — server-side mint flow.
|
|
3
|
+
*
|
|
4
|
+
* Encrypts credential payload locally with recipient's xidentity, then POSTs
|
|
5
|
+
* to upload-and-mint API. No registry, signer, or client-side mint needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AssetType } from './types.js';
|
|
9
|
+
import { ECIESEncryptor } from './encryption.js';
|
|
10
|
+
import { encryptAsset } from './encryption.js';
|
|
11
|
+
|
|
12
|
+
/** Payload shape agreed with Yault webapp */
|
|
13
|
+
export interface CredentialNftPayload {
|
|
14
|
+
user_cred: string;
|
|
15
|
+
mnemonic?: string | null;
|
|
16
|
+
index: number;
|
|
17
|
+
label: string;
|
|
18
|
+
/** Optional memo (e.g. "letter to future heirs") — encrypted together with credential, delivered when release triggers. */
|
|
19
|
+
memo?: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Result shape expected by Yault */
|
|
23
|
+
export interface CredentialMintResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
txId?: string;
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Options for server-side mint */
|
|
30
|
+
export interface MintCredentialNftViaServerOptions {
|
|
31
|
+
/** Recipient's xidentity (base64). Required. */
|
|
32
|
+
xidentity: string;
|
|
33
|
+
/** Upload-and-mint API URL. If omitted, uses built-in prod/dev endpoint. */
|
|
34
|
+
uploadAndMintApiUrl?: string;
|
|
35
|
+
/** When true, uses built-in dev endpoint (if uploadAndMintApiUrl not set). */
|
|
36
|
+
dev?: boolean;
|
|
37
|
+
/** Optional: custom headers (e.g. Authorization) for the API request */
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
/** Network for mint (mainnet | devnet). Default: mainnet */
|
|
40
|
+
network?: 'mainnet' | 'devnet';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Options for preparing payload only (no POST). Same as MintCredentialNftViaServerOptions minus upload/headers. */
|
|
44
|
+
export interface PrepareCredentialNftPayloadOptions {
|
|
45
|
+
/** Recipient's xidentity (base64). Required. */
|
|
46
|
+
xidentity: string;
|
|
47
|
+
/** Network for mint (mainnet | devnet). Default: mainnet */
|
|
48
|
+
network?: 'mainnet' | 'devnet';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Result of prepareCredentialNftPayload: the exact body to POST to upload-and-mint later. */
|
|
52
|
+
export interface PreparedCredentialNftPayload {
|
|
53
|
+
/** Request body to POST to upload-and-mint API (data, contentType, tags, leafOwner, name, description, network). */
|
|
54
|
+
body: Record<string, unknown>;
|
|
55
|
+
/** Recipient Solana address (leafOwner). */
|
|
56
|
+
recipientSolanaAddress: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Built-in endpoints for upload-and-mint API */
|
|
60
|
+
export const RWA_UPLOAD_AND_MINT_ENDPOINTS = {
|
|
61
|
+
prod: 'https://api.yallet.xyz/api/v1/storage/rwa/upload-and-mint',
|
|
62
|
+
dev: 'https://api.yallet.xyz/api/v1/storage/rwa/upload-and-mint',
|
|
63
|
+
} as const;
|
|
64
|
+
|
|
65
|
+
const NOTE_TITLE = 'Yault Release Credentials';
|
|
66
|
+
|
|
67
|
+
function bytesToBase64(bytes: Uint8Array): string {
|
|
68
|
+
let binary = '';
|
|
69
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
70
|
+
binary += String.fromCharCode(bytes[i]);
|
|
71
|
+
}
|
|
72
|
+
return btoa(binary);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build the same upload-and-mint request body as mintCredentialNftViaServer, without sending.
|
|
77
|
+
* Used to store the encrypted note server-side (e.g. Oracle binding) and send only after attestation.
|
|
78
|
+
*
|
|
79
|
+
* @param recipientSolanaAddress - Recipient's Solana address (leafOwner)
|
|
80
|
+
* @param payload - { user_cred, mnemonic?, index, label }
|
|
81
|
+
* @param options - { xidentity, network? }
|
|
82
|
+
* @returns { body, recipientSolanaAddress } — body is the exact JSON to POST to upload-and-mint later
|
|
83
|
+
*/
|
|
84
|
+
export async function prepareCredentialNftPayload(
|
|
85
|
+
recipientSolanaAddress: string,
|
|
86
|
+
payload: CredentialNftPayload,
|
|
87
|
+
options: PrepareCredentialNftPayloadOptions
|
|
88
|
+
): Promise<PreparedCredentialNftPayload> {
|
|
89
|
+
const { xidentity, network = 'mainnet' } = options;
|
|
90
|
+
|
|
91
|
+
if (!xidentity || !xidentity.trim()) {
|
|
92
|
+
throw new Error('xidentity is required');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const note = {
|
|
96
|
+
title: NOTE_TITLE,
|
|
97
|
+
content: JSON.stringify(payload),
|
|
98
|
+
metadata: { createdAt: new Date().toISOString() },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const name = `${NOTE_TITLE} — ${payload.label}`;
|
|
102
|
+
const description = `Path index ${payload.index}. Decrypt in Yallet to view credentials.`;
|
|
103
|
+
|
|
104
|
+
const encryptor = new ECIESEncryptor();
|
|
105
|
+
const encrypted = await encryptAsset(note, AssetType.NOTE, xidentity.trim(), encryptor);
|
|
106
|
+
|
|
107
|
+
const payloadForStorage = {
|
|
108
|
+
encrypted: {
|
|
109
|
+
ephemeral_pub: encrypted.ephemeral_pub,
|
|
110
|
+
encrypted_aes_key: encrypted.encrypted_aes_key,
|
|
111
|
+
iv: encrypted.iv,
|
|
112
|
+
encrypted_data: encrypted.encrypted_data,
|
|
113
|
+
},
|
|
114
|
+
metadata: {
|
|
115
|
+
assetType: AssetType.NOTE,
|
|
116
|
+
direction: 'received' as const,
|
|
117
|
+
network,
|
|
118
|
+
version: encrypted.version,
|
|
119
|
+
uuid: encrypted.uuid,
|
|
120
|
+
timestamp: encrypted.timestamp,
|
|
121
|
+
dataSize: encrypted.dataSize,
|
|
122
|
+
uploadedAt: Date.now(),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const jsonString = JSON.stringify(payloadForStorage);
|
|
127
|
+
const dataBase64 = bytesToBase64(new TextEncoder().encode(jsonString));
|
|
128
|
+
|
|
129
|
+
const body: Record<string, unknown> = {
|
|
130
|
+
data: dataBase64,
|
|
131
|
+
contentType: 'application/json',
|
|
132
|
+
tags: { 'Yallet-Type': 'encrypted-asset', 'Asset-Type': AssetType.NOTE, 'Direction': 'received' },
|
|
133
|
+
leafOwner: recipientSolanaAddress,
|
|
134
|
+
name,
|
|
135
|
+
description,
|
|
136
|
+
network,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return { body, recipientSolanaAddress };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Mint credential payload via server (upload-and-mint API).
|
|
144
|
+
*
|
|
145
|
+
* Caller provides xidentity from recipient-addresses. No registry or signer.
|
|
146
|
+
*
|
|
147
|
+
* @param recipientSolanaAddress - Recipient's Solana address (leafOwner)
|
|
148
|
+
* @param payload - { user_cred, mnemonic?, index, label }
|
|
149
|
+
* @param options - { xidentity, uploadAndMintApiUrl?, dev?, headers?, network? }
|
|
150
|
+
* @returns { success, txId?, error? }
|
|
151
|
+
*/
|
|
152
|
+
export async function mintCredentialNftViaServer(
|
|
153
|
+
recipientSolanaAddress: string,
|
|
154
|
+
payload: CredentialNftPayload,
|
|
155
|
+
options: MintCredentialNftViaServerOptions
|
|
156
|
+
): Promise<CredentialMintResult> {
|
|
157
|
+
const { xidentity, uploadAndMintApiUrl, dev = false, headers = {}, network = 'mainnet' } = options;
|
|
158
|
+
|
|
159
|
+
if (!xidentity || !xidentity.trim()) {
|
|
160
|
+
return { success: false, error: 'xidentity is required' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const apiUrl =
|
|
164
|
+
uploadAndMintApiUrl ||
|
|
165
|
+
(dev ? RWA_UPLOAD_AND_MINT_ENDPOINTS.dev : RWA_UPLOAD_AND_MINT_ENDPOINTS.prod);
|
|
166
|
+
|
|
167
|
+
const { body } = await prepareCredentialNftPayload(recipientSolanaAddress, payload, { xidentity, network });
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch(apiUrl, {
|
|
171
|
+
method: 'POST',
|
|
172
|
+
headers: {
|
|
173
|
+
'Content-Type': 'application/json',
|
|
174
|
+
...headers,
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify(body),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const result = (await response.json().catch(() => ({}))) as {
|
|
180
|
+
success?: boolean;
|
|
181
|
+
mint?: { signature?: string };
|
|
182
|
+
error?: string;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
error: result?.error || response.statusText || 'Upload-and-mint failed',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const signature = result?.mint?.signature;
|
|
193
|
+
return {
|
|
194
|
+
success: true,
|
|
195
|
+
txId: signature,
|
|
196
|
+
};
|
|
197
|
+
} catch (err) {
|
|
198
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
199
|
+
return { success: false, error: message };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yault credential NFT — mint release credentials (user_cred + optional mnemonic)
|
|
3
|
+
* as an encrypted message cNFT to the recipient.
|
|
4
|
+
*
|
|
5
|
+
* Used by Yault webapp after Plan submit: custody WASM generates credentials,
|
|
6
|
+
* this function mints them to the recipient's Solana address via RWA SDK mintMessage.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { YalletRWASDK } from './sdk.js';
|
|
10
|
+
import type { MintResult } from './types.js';
|
|
11
|
+
|
|
12
|
+
/** Payload shape agreed with Yault webapp (mnemonic optional until path design includes it). */
|
|
13
|
+
export interface CredentialNftPayload {
|
|
14
|
+
user_cred: string;
|
|
15
|
+
mnemonic?: string | null;
|
|
16
|
+
index: number;
|
|
17
|
+
label: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Result shape expected by Yault: success, txId (assetId or signature), error. */
|
|
21
|
+
export interface CredentialMintResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
txId?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const NOTE_TITLE = 'Yault Release Credentials';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mint credential payload as an encrypted note cNFT to the recipient.
|
|
31
|
+
*
|
|
32
|
+
* @param sdk - Initialized YalletRWASDK (config, signer, registry set)
|
|
33
|
+
* @param recipientSolanaAddress - Recipient's Solana address (must be registered in SDK registry with xidentity)
|
|
34
|
+
* @param payload - { user_cred, mnemonic?, index, label }
|
|
35
|
+
* @returns { success, txId?, error? } for Yault UI
|
|
36
|
+
*/
|
|
37
|
+
export async function mintCredentialNft(
|
|
38
|
+
sdk: YalletRWASDK,
|
|
39
|
+
recipientSolanaAddress: string,
|
|
40
|
+
payload: CredentialNftPayload
|
|
41
|
+
): Promise<CredentialMintResult> {
|
|
42
|
+
const note = {
|
|
43
|
+
title: NOTE_TITLE,
|
|
44
|
+
content: JSON.stringify(payload),
|
|
45
|
+
metadata: { createdAt: new Date().toISOString() },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const result: MintResult = await sdk.mintMessage(note, recipientSolanaAddress, {
|
|
49
|
+
name: `${NOTE_TITLE} — ${payload.label}`,
|
|
50
|
+
description: `Path index ${payload.index}. Decrypt in Yallet to view credentials.`,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (result.success) {
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
txId: result.assetId ?? result.signature ?? undefined,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: result.error ?? 'Mint failed',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yallet RWA SDK - Encryption Module
|
|
3
|
+
*
|
|
4
|
+
* Provides ECIES encryption for RWA assets using xidentity public keys.
|
|
5
|
+
* Compatible with the Yallet WASM encryption module.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
AssetType,
|
|
10
|
+
AssetEnvelope,
|
|
11
|
+
EncryptedPayload,
|
|
12
|
+
EncryptFunction,
|
|
13
|
+
} from './types.js';
|
|
14
|
+
|
|
15
|
+
// ======================================
|
|
16
|
+
// Pure JavaScript ECIES Implementation
|
|
17
|
+
// ======================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ECIES encryption using Web Crypto API.
|
|
21
|
+
* Must match dev.yallet.chrome-extension (acegf WASM) so the extension can decrypt:
|
|
22
|
+
* dh_key = SHA256(shared_secret), encrypted_aes_key = AES-GCM(dh_key).encrypt(iv, aes_key), encrypted_data = AES-GCM(aes_key).encrypt(iv, plaintext).
|
|
23
|
+
*/
|
|
24
|
+
export class ECIESEncryptor {
|
|
25
|
+
/**
|
|
26
|
+
* Encrypt data for a recipient's xidentity public key.
|
|
27
|
+
* Scheme (same as acegf encrypt): ECDH → SHA256(dh) = master_key → AES-GCM(master_key).encrypt(iv, random_aes_key) → AES-GCM(aes_key).encrypt(iv, plaintext).
|
|
28
|
+
*
|
|
29
|
+
* @param recipientXidentityB64 - Recipient's X25519 public key (base64)
|
|
30
|
+
* @param plaintext - Data to encrypt
|
|
31
|
+
* @returns Encrypted payload
|
|
32
|
+
*/
|
|
33
|
+
async encrypt(
|
|
34
|
+
recipientXidentityB64: string,
|
|
35
|
+
plaintext: Uint8Array
|
|
36
|
+
): Promise<EncryptedPayload> {
|
|
37
|
+
const recipientPubKey = base64ToBytes(recipientXidentityB64);
|
|
38
|
+
|
|
39
|
+
const ephemeralKeyPair = (await crypto.subtle.generateKey(
|
|
40
|
+
{ name: 'X25519' },
|
|
41
|
+
true,
|
|
42
|
+
['deriveBits']
|
|
43
|
+
)) as CryptoKeyPair;
|
|
44
|
+
|
|
45
|
+
const ephemeralPubKeyRaw = await crypto.subtle.exportKey(
|
|
46
|
+
'raw',
|
|
47
|
+
ephemeralKeyPair.publicKey
|
|
48
|
+
);
|
|
49
|
+
const ephemeralPubKey = new Uint8Array(ephemeralPubKeyRaw);
|
|
50
|
+
|
|
51
|
+
const recipientKey = await crypto.subtle.importKey(
|
|
52
|
+
'raw',
|
|
53
|
+
recipientPubKey as BufferSource,
|
|
54
|
+
{ name: 'X25519' },
|
|
55
|
+
false,
|
|
56
|
+
[]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const sharedSecretRaw = await crypto.subtle.deriveBits(
|
|
60
|
+
{ name: 'X25519', public: recipientKey },
|
|
61
|
+
ephemeralKeyPair.privateKey,
|
|
62
|
+
256
|
|
63
|
+
);
|
|
64
|
+
const sharedSecret = new Uint8Array(sharedSecretRaw);
|
|
65
|
+
|
|
66
|
+
// Match extension WASM: dh_key = SHA256(shared_secret), then AES-GCM(dh_key) encrypts the random AES key
|
|
67
|
+
const dhKeyHash = await crypto.subtle.digest('SHA-256', sharedSecret);
|
|
68
|
+
const dhKey = new Uint8Array(dhKeyHash);
|
|
69
|
+
|
|
70
|
+
const masterKey = await crypto.subtle.importKey(
|
|
71
|
+
'raw',
|
|
72
|
+
dhKey,
|
|
73
|
+
{ name: 'AES-GCM' },
|
|
74
|
+
false,
|
|
75
|
+
['encrypt']
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const aesKeyBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
79
|
+
const aesKey = await crypto.subtle.importKey(
|
|
80
|
+
'raw',
|
|
81
|
+
aesKeyBytes,
|
|
82
|
+
{ name: 'AES-GCM' },
|
|
83
|
+
false,
|
|
84
|
+
['encrypt']
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
88
|
+
|
|
89
|
+
const encryptedAesKeyRaw = await crypto.subtle.encrypt(
|
|
90
|
+
{ name: 'AES-GCM', iv },
|
|
91
|
+
masterKey,
|
|
92
|
+
aesKeyBytes
|
|
93
|
+
);
|
|
94
|
+
const encryptedAesKey = new Uint8Array(encryptedAesKeyRaw);
|
|
95
|
+
|
|
96
|
+
const encryptedDataRaw = await crypto.subtle.encrypt(
|
|
97
|
+
{ name: 'AES-GCM', iv },
|
|
98
|
+
aesKey,
|
|
99
|
+
plaintext as BufferSource
|
|
100
|
+
);
|
|
101
|
+
const encryptedData = new Uint8Array(encryptedDataRaw);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
ephemeral_pub: bytesToBase64(ephemeralPubKey),
|
|
105
|
+
encrypted_aes_key: bytesToBase64(encryptedAesKey),
|
|
106
|
+
iv: bytesToBase64(iv),
|
|
107
|
+
encrypted_data: bytesToBase64(encryptedData),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ======================================
|
|
113
|
+
// WASM-Compatible Encryption Wrapper
|
|
114
|
+
// ======================================
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Wrapper for WASM encrypt function
|
|
118
|
+
*
|
|
119
|
+
* Use this when you have access to the Yallet WASM module
|
|
120
|
+
*/
|
|
121
|
+
export function createWasmEncryptor(wasmEncryptFn: EncryptFunction) {
|
|
122
|
+
return {
|
|
123
|
+
encrypt(
|
|
124
|
+
recipientXidentityB64: string,
|
|
125
|
+
plaintext: Uint8Array
|
|
126
|
+
): EncryptedPayload {
|
|
127
|
+
const result = wasmEncryptFn(recipientXidentityB64, plaintext);
|
|
128
|
+
|
|
129
|
+
if (result.startsWith('error:')) {
|
|
130
|
+
throw new Error(`Encryption failed: ${result.substring(6)}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return JSON.parse(result) as EncryptedPayload;
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Initialize acegf WASM and return the encrypt function for createRWASDK.
|
|
140
|
+
* Pass the default export of acegf.js (init) and optionally the wasm path/buffer.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* import init from './wasm/acegf.js';
|
|
145
|
+
* import { createAcegfEncryptor, createRWASDK } from '@yallet/rwa-sdk';
|
|
146
|
+
* const wasmEncryptFn = await createAcegfEncryptor(init, '/path/to/acegf_bg.wasm');
|
|
147
|
+
* const sdk = createRWASDK(config, { wasmEncryptFn });
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export async function createAcegfEncryptor(
|
|
151
|
+
initFn: (
|
|
152
|
+
moduleOrPath?: string | URL | ArrayBuffer | object
|
|
153
|
+
) => Promise<{ acegf_encrypt_for_xidentity: EncryptFunction }>,
|
|
154
|
+
wasmModuleOrPath?: string | URL | ArrayBuffer | object
|
|
155
|
+
): Promise<EncryptFunction> {
|
|
156
|
+
const acegf = await initFn(wasmModuleOrPath);
|
|
157
|
+
return acegf.acegf_encrypt_for_xidentity;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ======================================
|
|
161
|
+
// Asset Preparation
|
|
162
|
+
// ======================================
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generate a UUID v4
|
|
166
|
+
*/
|
|
167
|
+
function generateUUID(): string {
|
|
168
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
169
|
+
return crypto.randomUUID();
|
|
170
|
+
}
|
|
171
|
+
// Fallback for environments without crypto.randomUUID
|
|
172
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
173
|
+
const r = (Math.random() * 16) | 0;
|
|
174
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
175
|
+
return v.toString(16);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Options for preparing asset data
|
|
181
|
+
*/
|
|
182
|
+
export interface PrepareAssetOptions {
|
|
183
|
+
/** Custom UUID (auto-generated if not provided) */
|
|
184
|
+
uuid?: string;
|
|
185
|
+
/** Custom timestamp (current time if not provided) */
|
|
186
|
+
timestamp?: number;
|
|
187
|
+
/** Schema identifier */
|
|
188
|
+
schema?: string;
|
|
189
|
+
/** Previous token for version chain */
|
|
190
|
+
previousToken?: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Prepare asset data for encryption
|
|
195
|
+
*
|
|
196
|
+
* @param asset - Asset data object
|
|
197
|
+
* @param assetType - Type of asset
|
|
198
|
+
* @param options - Optional configuration
|
|
199
|
+
* @returns Object containing serialized data and metadata
|
|
200
|
+
*/
|
|
201
|
+
export function prepareAssetData(
|
|
202
|
+
asset: unknown,
|
|
203
|
+
assetType: AssetType,
|
|
204
|
+
options: PrepareAssetOptions = {}
|
|
205
|
+
): { data: Uint8Array; uuid: string; timestamp: number } {
|
|
206
|
+
const uuid = options.uuid || generateUUID();
|
|
207
|
+
const timestamp = options.timestamp || Date.now();
|
|
208
|
+
|
|
209
|
+
const envelope: AssetEnvelope = {
|
|
210
|
+
version: 1,
|
|
211
|
+
type: assetType,
|
|
212
|
+
timestamp,
|
|
213
|
+
uuid,
|
|
214
|
+
schema: options.schema,
|
|
215
|
+
previousToken: options.previousToken,
|
|
216
|
+
data: asset,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const jsonString = JSON.stringify(envelope);
|
|
220
|
+
return {
|
|
221
|
+
data: new TextEncoder().encode(jsonString),
|
|
222
|
+
uuid,
|
|
223
|
+
timestamp,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Options for encrypting an asset
|
|
229
|
+
*/
|
|
230
|
+
export interface EncryptAssetOptions {
|
|
231
|
+
/** Custom UUID (auto-generated if not provided) */
|
|
232
|
+
uuid?: string;
|
|
233
|
+
/** Custom timestamp (current time if not provided) */
|
|
234
|
+
timestamp?: number;
|
|
235
|
+
/** Schema identifier */
|
|
236
|
+
schema?: string;
|
|
237
|
+
/** Previous token for version chain */
|
|
238
|
+
previousToken?: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Result of encrypting an asset
|
|
243
|
+
*/
|
|
244
|
+
export interface EncryptedAssetResult extends EncryptedPayload {
|
|
245
|
+
assetType: AssetType;
|
|
246
|
+
version: number;
|
|
247
|
+
uuid: string;
|
|
248
|
+
timestamp: number;
|
|
249
|
+
dataSize: number;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Encrypt an asset for a recipient
|
|
254
|
+
*
|
|
255
|
+
* @param asset - Asset data
|
|
256
|
+
* @param assetType - Type of asset
|
|
257
|
+
* @param recipientXidentity - Recipient's xidentity (base64)
|
|
258
|
+
* @param encryptor - Encryptor instance (WASM or JS)
|
|
259
|
+
* @param options - Optional configuration
|
|
260
|
+
* @returns Encrypted payload with metadata
|
|
261
|
+
*/
|
|
262
|
+
export async function encryptAsset(
|
|
263
|
+
asset: unknown,
|
|
264
|
+
assetType: AssetType,
|
|
265
|
+
recipientXidentity: string,
|
|
266
|
+
encryptor: ECIESEncryptor | ReturnType<typeof createWasmEncryptor>,
|
|
267
|
+
options: EncryptAssetOptions = {}
|
|
268
|
+
): Promise<EncryptedAssetResult> {
|
|
269
|
+
const prepared = prepareAssetData(asset, assetType, options);
|
|
270
|
+
|
|
271
|
+
let encrypted: EncryptedPayload;
|
|
272
|
+
|
|
273
|
+
if (encryptor instanceof ECIESEncryptor) {
|
|
274
|
+
encrypted = await encryptor.encrypt(recipientXidentity, prepared.data);
|
|
275
|
+
} else {
|
|
276
|
+
encrypted = encryptor.encrypt(recipientXidentity, prepared.data);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
...encrypted,
|
|
281
|
+
assetType,
|
|
282
|
+
version: 1,
|
|
283
|
+
uuid: prepared.uuid,
|
|
284
|
+
timestamp: prepared.timestamp,
|
|
285
|
+
dataSize: prepared.data.length,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ======================================
|
|
290
|
+
// Utility Functions
|
|
291
|
+
// ======================================
|
|
292
|
+
|
|
293
|
+
function base64ToBytes(base64: string): Uint8Array {
|
|
294
|
+
const binaryString = atob(base64);
|
|
295
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
296
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
297
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
298
|
+
}
|
|
299
|
+
return bytes;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function bytesToBase64(bytes: Uint8Array): string {
|
|
303
|
+
let binaryString = '';
|
|
304
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
305
|
+
binaryString += String.fromCharCode(bytes[i]);
|
|
306
|
+
}
|
|
307
|
+
return btoa(binaryString);
|
|
308
|
+
}
|