@fide.work/fcp 0.0.1-alpha.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 +18 -0
- package/dist/attestation/create.d.ts +173 -0
- package/dist/attestation/create.js +179 -0
- package/dist/attestation/index.d.ts +7 -0
- package/dist/attestation/index.js +7 -0
- package/dist/attestation/verify.d.ts +56 -0
- package/dist/attestation/verify.js +94 -0
- package/dist/broadcasting/config.d.ts +27 -0
- package/dist/broadcasting/config.js +22 -0
- package/dist/broadcasting/index.d.ts +7 -0
- package/dist/broadcasting/index.js +7 -0
- package/dist/broadcasting/registry.d.ts +111 -0
- package/dist/broadcasting/registry.js +99 -0
- package/dist/experimental/attestation/create.d.ts +173 -0
- package/dist/experimental/attestation/create.js +188 -0
- package/dist/experimental/attestation/index.d.ts +7 -0
- package/dist/experimental/attestation/index.js +7 -0
- package/dist/experimental/attestation/verify.d.ts +56 -0
- package/dist/experimental/attestation/verify.js +94 -0
- package/dist/experimental/broadcasting/config.d.ts +27 -0
- package/dist/experimental/broadcasting/config.js +22 -0
- package/dist/experimental/broadcasting/index.d.ts +7 -0
- package/dist/experimental/broadcasting/index.js +7 -0
- package/dist/experimental/broadcasting/registry.d.ts +111 -0
- package/dist/experimental/broadcasting/registry.js +99 -0
- package/dist/experimental/index.d.ts +9 -0
- package/dist/experimental/index.js +13 -0
- package/dist/experimental/merkle/index.d.ts +6 -0
- package/dist/experimental/merkle/index.js +6 -0
- package/dist/experimental/merkle/tree.d.ts +72 -0
- package/dist/experimental/merkle/tree.js +154 -0
- package/dist/experimental/signing/ed25519.d.ts +116 -0
- package/dist/experimental/signing/ed25519.js +161 -0
- package/dist/experimental/signing/eip191.d.ts +50 -0
- package/dist/experimental/signing/eip191.js +96 -0
- package/dist/experimental/signing/eip712.d.ts +112 -0
- package/dist/experimental/signing/eip712.js +187 -0
- package/dist/experimental/signing/index.d.ts +8 -0
- package/dist/experimental/signing/index.js +11 -0
- package/dist/fide-id/calculateFideId.d.ts +21 -0
- package/dist/fide-id/calculateFideId.js +53 -0
- package/dist/fide-id/calculateStatementFideId.d.ts +21 -0
- package/dist/fide-id/calculateStatementFideId.js +38 -0
- package/dist/fide-id/constants.d.ts +43 -0
- package/dist/fide-id/constants.js +55 -0
- package/dist/fide-id/index.d.ts +10 -0
- package/dist/fide-id/index.js +12 -0
- package/dist/fide-id/types.d.ts +52 -0
- package/dist/fide-id/types.js +5 -0
- package/dist/fide-id/utils.d.ts +30 -0
- package/dist/fide-id/utils.js +65 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +19 -0
- package/dist/merkle/index.d.ts +6 -0
- package/dist/merkle/index.js +6 -0
- package/dist/merkle/tree.d.ts +72 -0
- package/dist/merkle/tree.js +154 -0
- package/dist/schema/evaluations.d.ts +31 -0
- package/dist/schema/evaluations.js +31 -0
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.js +6 -0
- package/dist/schema/predicates.d.ts +110 -0
- package/dist/schema/predicates.js +122 -0
- package/dist/signing/ed25519.d.ts +116 -0
- package/dist/signing/ed25519.js +161 -0
- package/dist/signing/eip191.d.ts +50 -0
- package/dist/signing/eip191.js +96 -0
- package/dist/signing/eip712.d.ts +112 -0
- package/dist/signing/eip712.js +187 -0
- package/dist/signing/index.d.ts +8 -0
- package/dist/signing/index.js +11 -0
- package/dist/statement/build.d.ts +99 -0
- package/dist/statement/build.js +71 -0
- package/dist/statement/index.d.ts +6 -0
- package/dist/statement/index.js +6 -0
- package/dist/statement/policy.d.ts +33 -0
- package/dist/statement/policy.js +183 -0
- package/package.json +64 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP SDK Attestation Verification Utilities
|
|
3
|
+
*
|
|
4
|
+
* Full verification functions that combine Merkle proof and signature verification.
|
|
5
|
+
*/
|
|
6
|
+
import { verifyStatementInAttestation, parseAttestationData } from "./create.js";
|
|
7
|
+
import { verifyEd25519 } from "../signing/ed25519.js";
|
|
8
|
+
import { verifyEip712 } from "../signing/eip712.js";
|
|
9
|
+
import { verifyEip191 } from "../signing/eip191.js";
|
|
10
|
+
/**
|
|
11
|
+
* Verify a complete attestation (both Merkle proof and signature)
|
|
12
|
+
*
|
|
13
|
+
* This function performs full verification:
|
|
14
|
+
* 1. Verifies the Merkle proof (statement is in the batch)
|
|
15
|
+
* 2. Verifies the signature (signer authorized the batch)
|
|
16
|
+
*
|
|
17
|
+
* @param statementFideId - The leaf value to verify in Merkle tree
|
|
18
|
+
* @param proof - The Merkle proof path (array of hashes from leaf to root)
|
|
19
|
+
* @param attestationData - The attestation data structure
|
|
20
|
+
* @param options - Configuration options for operation (method, public key/address, etc.)
|
|
21
|
+
* @returns True if both Merkle proof and signature are valid
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* // With Ed25519
|
|
26
|
+
* const isValid = await verifyAttestation(
|
|
27
|
+
* statementFideId,
|
|
28
|
+
* proof,
|
|
29
|
+
* attestationData,
|
|
30
|
+
* {
|
|
31
|
+
* method: 'ed25519',
|
|
32
|
+
* publicKeyOrAddress: publicKey
|
|
33
|
+
* }
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* // With EIP-712
|
|
37
|
+
* const isValid = await verifyAttestation(
|
|
38
|
+
* statementFideId,
|
|
39
|
+
* proof,
|
|
40
|
+
* attestationData,
|
|
41
|
+
* {
|
|
42
|
+
* method: 'eip712',
|
|
43
|
+
* publicKeyOrAddress: address
|
|
44
|
+
* }
|
|
45
|
+
* );
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export async function verifyAttestation(statementFideId, proof, attestationData, options) {
|
|
49
|
+
// Parse attestation data if it's a string
|
|
50
|
+
const data = typeof attestationData === 'string'
|
|
51
|
+
? parseAttestationData(attestationData)
|
|
52
|
+
: attestationData;
|
|
53
|
+
// 1. Verify Merkle proof
|
|
54
|
+
const merkleValid = await verifyStatementInAttestation(statementFideId, proof, data);
|
|
55
|
+
if (!merkleValid) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
// 2. Verify signature based on method
|
|
59
|
+
let signatureValid = false;
|
|
60
|
+
if (options.method === 'ed25519') {
|
|
61
|
+
if (typeof options.publicKeyOrAddress === 'string') {
|
|
62
|
+
throw new Error('Ed25519 verification requires a CryptoKey, not an address string');
|
|
63
|
+
}
|
|
64
|
+
signatureValid = await verifyEd25519(data.r, // merkle root
|
|
65
|
+
data.s, // signature
|
|
66
|
+
options.publicKeyOrAddress);
|
|
67
|
+
}
|
|
68
|
+
else if (options.method === 'eip712') {
|
|
69
|
+
if (typeof options.publicKeyOrAddress !== 'string') {
|
|
70
|
+
throw new Error('EIP-712 verification requires an address string, not a CryptoKey');
|
|
71
|
+
}
|
|
72
|
+
if (!data.s.startsWith('0x')) {
|
|
73
|
+
throw new Error('EIP-712 signature must be hex-encoded (0x prefix)');
|
|
74
|
+
}
|
|
75
|
+
signatureValid = await verifyEip712(data.r, // merkle root
|
|
76
|
+
data.s, // signature
|
|
77
|
+
options.publicKeyOrAddress);
|
|
78
|
+
}
|
|
79
|
+
else if (options.method === 'eip191') {
|
|
80
|
+
if (typeof options.publicKeyOrAddress !== 'string') {
|
|
81
|
+
throw new Error('EIP-191 verification requires an address string, not a CryptoKey');
|
|
82
|
+
}
|
|
83
|
+
if (!data.s.startsWith('0x')) {
|
|
84
|
+
throw new Error('EIP-191 signature must be hex-encoded (0x prefix)');
|
|
85
|
+
}
|
|
86
|
+
signatureValid = await verifyEip191(data.r, // merkle root
|
|
87
|
+
data.s, // signature
|
|
88
|
+
options.publicKeyOrAddress);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
throw new Error(`Unsupported signing method: ${options.method}`);
|
|
92
|
+
}
|
|
93
|
+
return signatureValid;
|
|
94
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP Registry Configuration
|
|
3
|
+
*
|
|
4
|
+
* Allows registries to specify where attestations are located
|
|
5
|
+
* and other registry metadata.
|
|
6
|
+
*/
|
|
7
|
+
export interface FCPRegistryConfig {
|
|
8
|
+
/** Path to attestations directory (relative to repo root) */
|
|
9
|
+
attestationsPath?: string;
|
|
10
|
+
/** Optional registry metadata */
|
|
11
|
+
metadata?: {
|
|
12
|
+
name?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Default attestation paths to check if no config file exists
|
|
18
|
+
*/
|
|
19
|
+
export declare const DEFAULT_ATTESTATION_PATHS: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Default attestations path (used when generating paths)
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_ATTESTATIONS_PATH = "attestations";
|
|
24
|
+
/**
|
|
25
|
+
* Registry config filename
|
|
26
|
+
*/
|
|
27
|
+
export declare const REGISTRY_CONFIG_FILENAME = ".fcp-registry.json";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP Registry Configuration
|
|
3
|
+
*
|
|
4
|
+
* Allows registries to specify where attestations are located
|
|
5
|
+
* and other registry metadata.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Default attestation paths to check if no config file exists
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_ATTESTATION_PATHS = [
|
|
11
|
+
'attestations',
|
|
12
|
+
'fcp-attestations',
|
|
13
|
+
'.fcp/attestations'
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Default attestations path (used when generating paths)
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_ATTESTATIONS_PATH = 'attestations';
|
|
19
|
+
/**
|
|
20
|
+
* Registry config filename
|
|
21
|
+
*/
|
|
22
|
+
export const REGISTRY_CONFIG_FILENAME = '.fcp-registry.json';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP SDK - Broadcasting Module
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all broadcasting utilities.
|
|
5
|
+
*/
|
|
6
|
+
export { formatAttestationForJSONL, generateRegistryPath, generateJSONLFilename, type JSONLAttestation, type JSONLStatement } from "./registry.js";
|
|
7
|
+
export { DEFAULT_ATTESTATION_PATHS, DEFAULT_ATTESTATIONS_PATH, REGISTRY_CONFIG_FILENAME, type FCPRegistryConfig } from "./config.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP SDK - Broadcasting Module
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all broadcasting utilities.
|
|
5
|
+
*/
|
|
6
|
+
export { formatAttestationForJSONL, generateRegistryPath, generateJSONLFilename } from "./registry.js";
|
|
7
|
+
export { DEFAULT_ATTESTATION_PATHS, DEFAULT_ATTESTATIONS_PATH, REGISTRY_CONFIG_FILENAME } from "./config.js";
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP SDK Broadcasting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helpers for formatting attestations for JSONL registry files.
|
|
5
|
+
*
|
|
6
|
+
* Note: This module provides formatting helpers. Actual Git operations
|
|
7
|
+
* (commit, push) should be handled by your application using libraries
|
|
8
|
+
* like `simple-git` or `isomorphic-git`.
|
|
9
|
+
*/
|
|
10
|
+
import type { AttestationResult } from "../attestation/index.js";
|
|
11
|
+
import type { Statement } from "../../statement/build.js";
|
|
12
|
+
/**
|
|
13
|
+
* Statement object in lean JSONL format
|
|
14
|
+
* Uses short keys for efficiency: s/sr, p/pr, o/or
|
|
15
|
+
*/
|
|
16
|
+
export interface JSONLStatement {
|
|
17
|
+
/** Subject Fide ID (full did:fide:0x... format) */
|
|
18
|
+
s: string;
|
|
19
|
+
/** Subject raw identifier */
|
|
20
|
+
sr: string;
|
|
21
|
+
/** Predicate Fide ID (full did:fide:0x... format) */
|
|
22
|
+
p: string;
|
|
23
|
+
/** Predicate raw identifier */
|
|
24
|
+
pr: string;
|
|
25
|
+
/** Object Fide ID (full did:fide:0x... format) */
|
|
26
|
+
o: string;
|
|
27
|
+
/** Object raw identifier */
|
|
28
|
+
or: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Lean attestation format for JSONL registry files
|
|
32
|
+
*
|
|
33
|
+
* Optimized for indexers: minimal verbosity, all data needed for verification
|
|
34
|
+
* and materialization. Each line is one attestation batch.
|
|
35
|
+
*
|
|
36
|
+
* Format matches canonical attestation data structure: {m, r, s, u}
|
|
37
|
+
* Indexer derives attestation Fide ID from these fields.
|
|
38
|
+
*/
|
|
39
|
+
export interface JSONLAttestation {
|
|
40
|
+
/** Method: Signing method (e.g., "ed25519", "eip712", "eip191") */
|
|
41
|
+
m: string;
|
|
42
|
+
/** User: CAIP-10 signer identifier */
|
|
43
|
+
u: string;
|
|
44
|
+
/** Root: Merkle root commitment */
|
|
45
|
+
r: string;
|
|
46
|
+
/** Signature: Cryptographic signature */
|
|
47
|
+
s: string;
|
|
48
|
+
/** Timestamp: ISO 8601 UTC timestamp */
|
|
49
|
+
t: string;
|
|
50
|
+
/** Data: Array of statements in this batch */
|
|
51
|
+
d: JSONLStatement[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Format an attestation result for JSONL output
|
|
55
|
+
*
|
|
56
|
+
* Converts an AttestationResult into the lean JSONL format optimized for indexers.
|
|
57
|
+
* Each line in the JSONL file should be one attestation batch (one signature covering
|
|
58
|
+
* multiple statements via Merkle root).
|
|
59
|
+
*
|
|
60
|
+
* Format uses short keys (m, u, r, s, t, d) matching the canonical attestation
|
|
61
|
+
* data structure. Indexer derives attestation Fide ID from {m, r, s, u}.
|
|
62
|
+
*
|
|
63
|
+
* @param attestationResult - The result from createAttestation
|
|
64
|
+
* @param statements - The original statements that were attested (must match statementFideIds order)
|
|
65
|
+
* @param signedAt - ISO timestamp when the attestation was signed (defaults to current time)
|
|
66
|
+
* @returns Formatted attestation ready for JSONL output
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const { statements } = await batchStatementsWithRoot([...]);
|
|
71
|
+
* const attestation = await createAttestation(statementFideIds, options);
|
|
72
|
+
* const jsonlAttestation = formatAttestationForJSONL(attestation, statements);
|
|
73
|
+
*
|
|
74
|
+
* // Write to JSONL file (one line per batch)
|
|
75
|
+
* fs.appendFileSync('attestation.jsonl', JSON.stringify(jsonlAttestation) + '\n');
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare function formatAttestationForJSONL(attestationResult: AttestationResult, statements: Statement[], signedAt?: string): JSONLAttestation;
|
|
79
|
+
/**
|
|
80
|
+
* Generate the registry directory path for a given date
|
|
81
|
+
*
|
|
82
|
+
* Follows the YYYY/MM/DD structure required for Fide attestation registries.
|
|
83
|
+
* Uses UTC timezone for consistency across timezones.
|
|
84
|
+
*
|
|
85
|
+
* @param date - Date object (defaults to current UTC date)
|
|
86
|
+
* @returns Directory path (e.g., "2024/01/15")
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const path = generateRegistryPath(new Date('2024-01-15T00:00:00Z'));
|
|
91
|
+
* // Result: "2024/01/15"
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function generateRegistryPath(date?: Date): string;
|
|
95
|
+
/**
|
|
96
|
+
* Generate a JSONL filename following the registry convention
|
|
97
|
+
*
|
|
98
|
+
* Format: YYYY-MM-DD-{HHmm}-{sequence}.jsonl
|
|
99
|
+
* Uses UTC timezone for consistency across timezones.
|
|
100
|
+
*
|
|
101
|
+
* @param date - Date object (defaults to current UTC date)
|
|
102
|
+
* @param sequence - Sequence number (defaults to 1)
|
|
103
|
+
* @returns Filename (e.g., "2024-01-15-1400-1.jsonl")
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const filename = generateJSONLFilename(new Date('2024-01-15T14:00:00Z'), 1);
|
|
108
|
+
* // Result: "2024-01-15-1400-1.jsonl"
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare function generateJSONLFilename(date?: Date, sequence?: number): string;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP SDK Broadcasting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helpers for formatting attestations for JSONL registry files.
|
|
5
|
+
*
|
|
6
|
+
* Note: This module provides formatting helpers. Actual Git operations
|
|
7
|
+
* (commit, push) should be handled by your application using libraries
|
|
8
|
+
* like `simple-git` or `isomorphic-git`.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Format an attestation result for JSONL output
|
|
12
|
+
*
|
|
13
|
+
* Converts an AttestationResult into the lean JSONL format optimized for indexers.
|
|
14
|
+
* Each line in the JSONL file should be one attestation batch (one signature covering
|
|
15
|
+
* multiple statements via Merkle root).
|
|
16
|
+
*
|
|
17
|
+
* Format uses short keys (m, u, r, s, t, d) matching the canonical attestation
|
|
18
|
+
* data structure. Indexer derives attestation Fide ID from {m, r, s, u}.
|
|
19
|
+
*
|
|
20
|
+
* @param attestationResult - The result from createAttestation
|
|
21
|
+
* @param statements - The original statements that were attested (must match statementFideIds order)
|
|
22
|
+
* @param signedAt - ISO timestamp when the attestation was signed (defaults to current time)
|
|
23
|
+
* @returns Formatted attestation ready for JSONL output
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { statements } = await batchStatementsWithRoot([...]);
|
|
28
|
+
* const attestation = await createAttestation(statementFideIds, options);
|
|
29
|
+
* const jsonlAttestation = formatAttestationForJSONL(attestation, statements);
|
|
30
|
+
*
|
|
31
|
+
* // Write to JSONL file (one line per batch)
|
|
32
|
+
* fs.appendFileSync('attestation.jsonl', JSON.stringify(jsonlAttestation) + '\n');
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function formatAttestationForJSONL(attestationResult, statements, signedAt = new Date().toISOString()) {
|
|
36
|
+
// Convert statements to lean JSONL format with short keys
|
|
37
|
+
const jsonlStatements = statements.map(stmt => ({
|
|
38
|
+
s: stmt.subjectFideId,
|
|
39
|
+
sr: stmt.subjectRawIdentifier,
|
|
40
|
+
p: stmt.predicateFideId,
|
|
41
|
+
pr: stmt.predicateRawIdentifier,
|
|
42
|
+
o: stmt.objectFideId,
|
|
43
|
+
or: stmt.objectRawIdentifier
|
|
44
|
+
}));
|
|
45
|
+
return {
|
|
46
|
+
m: attestationResult.attestationData.m,
|
|
47
|
+
u: attestationResult.attestationData.u,
|
|
48
|
+
r: attestationResult.merkleRoot,
|
|
49
|
+
s: attestationResult.attestationData.s,
|
|
50
|
+
t: signedAt,
|
|
51
|
+
d: jsonlStatements
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generate the registry directory path for a given date
|
|
56
|
+
*
|
|
57
|
+
* Follows the YYYY/MM/DD structure required for Fide attestation registries.
|
|
58
|
+
* Uses UTC timezone for consistency across timezones.
|
|
59
|
+
*
|
|
60
|
+
* @param date - Date object (defaults to current UTC date)
|
|
61
|
+
* @returns Directory path (e.g., "2024/01/15")
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const path = generateRegistryPath(new Date('2024-01-15T00:00:00Z'));
|
|
66
|
+
* // Result: "2024/01/15"
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function generateRegistryPath(date = new Date()) {
|
|
70
|
+
const year = date.getUTCFullYear();
|
|
71
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
|
72
|
+
const day = String(date.getUTCDate()).padStart(2, '0');
|
|
73
|
+
return `${year}/${month}/${day}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate a JSONL filename following the registry convention
|
|
77
|
+
*
|
|
78
|
+
* Format: YYYY-MM-DD-{HHmm}-{sequence}.jsonl
|
|
79
|
+
* Uses UTC timezone for consistency across timezones.
|
|
80
|
+
*
|
|
81
|
+
* @param date - Date object (defaults to current UTC date)
|
|
82
|
+
* @param sequence - Sequence number (defaults to 1)
|
|
83
|
+
* @returns Filename (e.g., "2024-01-15-1400-1.jsonl")
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const filename = generateJSONLFilename(new Date('2024-01-15T14:00:00Z'), 1);
|
|
88
|
+
* // Result: "2024-01-15-1400-1.jsonl"
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export function generateJSONLFilename(date = new Date(), sequence = 1) {
|
|
92
|
+
const year = date.getUTCFullYear();
|
|
93
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
|
94
|
+
const day = String(date.getUTCDate()).padStart(2, '0');
|
|
95
|
+
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
96
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
97
|
+
const timeWindow = `${hours}${minutes}`;
|
|
98
|
+
return `${year}-${month}-${day}-${timeWindow}-${sequence}.jsonl`;
|
|
99
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fide.work/fcp experimental helpers
|
|
3
|
+
*
|
|
4
|
+
* These APIs are intentionally non-core and may change.
|
|
5
|
+
*/
|
|
6
|
+
export { generateEd25519KeyPair, exportEd25519Keys, importEd25519Keys, signEd25519, verifyEd25519, getEthereumAddress, signEip712, verifyEip712, createEthereumCaip10, FCP_EIP712_DOMAIN, signEip191, verifyEip191, type Ed25519KeyPair, type ExportedEd25519Keys, type Eip712Domain, } from "./signing/index.js";
|
|
7
|
+
export { buildMerkleTree, verifyMerkleProof, type MerkleProofElement, type MerkleProof, type MerkleTreeResult, } from "./merkle/index.js";
|
|
8
|
+
export { createAttestation, createProvenanceStatements, verifyStatementInAttestation, verifyAttestation, parseAttestationData, verifyAttestationFideId, type SigningMethod, type AttestationData, type AttestationResult, type CreateAttestationOptions, type ProvenanceStatement, type VerifyAttestationOptions, } from "./attestation/index.js";
|
|
9
|
+
export { formatAttestationForJSONL, generateRegistryPath, generateJSONLFilename, DEFAULT_ATTESTATION_PATHS, DEFAULT_ATTESTATIONS_PATH, REGISTRY_CONFIG_FILENAME, type JSONLAttestation, type JSONLStatement, type FCPRegistryConfig, } from "./broadcasting/index.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fide.work/fcp experimental helpers
|
|
3
|
+
*
|
|
4
|
+
* These APIs are intentionally non-core and may change.
|
|
5
|
+
*/
|
|
6
|
+
// Signing helpers
|
|
7
|
+
export { generateEd25519KeyPair, exportEd25519Keys, importEd25519Keys, signEd25519, verifyEd25519, getEthereumAddress, signEip712, verifyEip712, createEthereumCaip10, FCP_EIP712_DOMAIN, signEip191, verifyEip191, } from "./signing/index.js";
|
|
8
|
+
// Merkle helpers
|
|
9
|
+
export { buildMerkleTree, verifyMerkleProof, } from "./merkle/index.js";
|
|
10
|
+
// Attestation helpers
|
|
11
|
+
export { createAttestation, createProvenanceStatements, verifyStatementInAttestation, verifyAttestation, parseAttestationData, verifyAttestationFideId, } from "./attestation/index.js";
|
|
12
|
+
// Broadcasting helpers
|
|
13
|
+
export { formatAttestationForJSONL, generateRegistryPath, generateJSONLFilename, DEFAULT_ATTESTATION_PATHS, DEFAULT_ATTESTATIONS_PATH, REGISTRY_CONFIG_FILENAME, } from "./broadcasting/index.js";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP SDK Merkle Tree Utilities
|
|
3
|
+
* Build and verify Merkle trees for batch attestations
|
|
4
|
+
*
|
|
5
|
+
* Uses SHA-256 for hashing (same as Fide ID derivation).
|
|
6
|
+
* Zero external dependencies - uses native Web Crypto API.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Merkle proof element
|
|
10
|
+
*/
|
|
11
|
+
export interface MerkleProofElement {
|
|
12
|
+
/** The sibling hash at this level */
|
|
13
|
+
hash: string;
|
|
14
|
+
/** Position of the sibling: 'left' or 'right' */
|
|
15
|
+
position: 'left' | 'right';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Merkle proof for a specific leaf
|
|
19
|
+
*/
|
|
20
|
+
export type MerkleProof = MerkleProofElement[];
|
|
21
|
+
/**
|
|
22
|
+
* Result from building a Merkle tree
|
|
23
|
+
*/
|
|
24
|
+
export interface MerkleTreeResult {
|
|
25
|
+
/** The Merkle root (hex string) */
|
|
26
|
+
root: string;
|
|
27
|
+
/** Proofs for each leaf, indexed by leaf value */
|
|
28
|
+
proofs: Map<string, MerkleProof>;
|
|
29
|
+
/** The leaves used to build the tree (normalized) */
|
|
30
|
+
leaves: string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build a Merkle tree from an array of Fide IDs
|
|
34
|
+
*
|
|
35
|
+
* The tree is built bottom-up by iteratively hashing pairs of nodes.
|
|
36
|
+
* If there's an odd number of nodes, the last node is duplicated.
|
|
37
|
+
*
|
|
38
|
+
* @param leaves - Array of statement Fide IDs to include in the tree
|
|
39
|
+
* @returns MerkleTreeResult with root and proofs for each leaf
|
|
40
|
+
* @throws Error if leaves array is empty
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const result = await buildMerkleTree([
|
|
45
|
+
* 'did:fide:0x00abc...',
|
|
46
|
+
* 'did:fide:0x00def...',
|
|
47
|
+
* 'did:fide:0x00ghi...'
|
|
48
|
+
* ]);
|
|
49
|
+
*
|
|
50
|
+
* console.log('Root:', result.root);
|
|
51
|
+
* console.log('Proof for first leaf:', result.proofs.get('did:fide:0x00abc...'));
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function buildMerkleTree(leaves: string[]): Promise<MerkleTreeResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Verify that a leaf is part of a Merkle tree given its proof
|
|
57
|
+
*
|
|
58
|
+
* @param leaf - The leaf value to verify in Merkle tree
|
|
59
|
+
* @param proof - The Merkle proof path (array of hashes from leaf to root)
|
|
60
|
+
* @param root - The Merkle tree root hash (0x-prefixed hex)
|
|
61
|
+
* @returns True if the proof is valid
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const isValid = await verifyMerkleProof(
|
|
66
|
+
* 'did:fide:0x00abc...',
|
|
67
|
+
* proof,
|
|
68
|
+
* root
|
|
69
|
+
* );
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function verifyMerkleProof(leaf: string, proof: MerkleProof, root: string): Promise<boolean>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FCP SDK Merkle Tree Utilities
|
|
3
|
+
* Build and verify Merkle trees for batch attestations
|
|
4
|
+
*
|
|
5
|
+
* Uses SHA-256 for hashing (same as Fide ID derivation).
|
|
6
|
+
* Zero external dependencies - uses native Web Crypto API.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Get the Web Crypto API (works in browsers and Node.js)
|
|
10
|
+
*/
|
|
11
|
+
async function getSubtleCrypto() {
|
|
12
|
+
if (globalThis.crypto?.subtle) {
|
|
13
|
+
return globalThis.crypto.subtle;
|
|
14
|
+
}
|
|
15
|
+
const { webcrypto } = await import("node:crypto");
|
|
16
|
+
return webcrypto.subtle;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Hash two values together using SHA-256
|
|
20
|
+
* Sorts inputs to ensure consistent ordering
|
|
21
|
+
*/
|
|
22
|
+
async function hashPair(left, right) {
|
|
23
|
+
const subtle = await getSubtleCrypto();
|
|
24
|
+
// Concatenate left + right (already sorted at construction time)
|
|
25
|
+
const combined = left + right;
|
|
26
|
+
const encoder = new TextEncoder();
|
|
27
|
+
const data = encoder.encode(combined);
|
|
28
|
+
const hashBuffer = await subtle.digest('SHA-256', data);
|
|
29
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
30
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Normalize a Fide ID to a consistent hex format for hashing
|
|
34
|
+
* Accepts: "did:fide:0x..." or "0x..."
|
|
35
|
+
* Returns: lowercase hex without prefix
|
|
36
|
+
*/
|
|
37
|
+
function normalizeLeaf(fideId) {
|
|
38
|
+
const trimmed = fideId.trim().toLowerCase();
|
|
39
|
+
if (trimmed.startsWith('did:fide:0x')) {
|
|
40
|
+
return trimmed.slice('did:fide:0x'.length);
|
|
41
|
+
}
|
|
42
|
+
if (trimmed.startsWith('0x')) {
|
|
43
|
+
return trimmed.slice(2);
|
|
44
|
+
}
|
|
45
|
+
return trimmed;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build a Merkle tree from an array of Fide IDs
|
|
49
|
+
*
|
|
50
|
+
* The tree is built bottom-up by iteratively hashing pairs of nodes.
|
|
51
|
+
* If there's an odd number of nodes, the last node is duplicated.
|
|
52
|
+
*
|
|
53
|
+
* @param leaves - Array of statement Fide IDs to include in the tree
|
|
54
|
+
* @returns MerkleTreeResult with root and proofs for each leaf
|
|
55
|
+
* @throws Error if leaves array is empty
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const result = await buildMerkleTree([
|
|
60
|
+
* 'did:fide:0x00abc...',
|
|
61
|
+
* 'did:fide:0x00def...',
|
|
62
|
+
* 'did:fide:0x00ghi...'
|
|
63
|
+
* ]);
|
|
64
|
+
*
|
|
65
|
+
* console.log('Root:', result.root);
|
|
66
|
+
* console.log('Proof for first leaf:', result.proofs.get('did:fide:0x00abc...'));
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export async function buildMerkleTree(leaves) {
|
|
70
|
+
if (leaves.length === 0) {
|
|
71
|
+
throw new Error('Cannot build Merkle tree from empty array');
|
|
72
|
+
}
|
|
73
|
+
// Normalize all leaves
|
|
74
|
+
const normalizedLeaves = leaves.map(normalizeLeaf);
|
|
75
|
+
// Initialize proofs map (keyed by original leaf value)
|
|
76
|
+
const proofs = new Map();
|
|
77
|
+
leaves.forEach(leaf => proofs.set(leaf, []));
|
|
78
|
+
// Also map normalized → original for lookup
|
|
79
|
+
const normalizedToOriginal = new Map();
|
|
80
|
+
leaves.forEach((original, i) => {
|
|
81
|
+
normalizedToOriginal.set(normalizedLeaves[i], original);
|
|
82
|
+
});
|
|
83
|
+
// Build tree level by level
|
|
84
|
+
let currentLevel = [...normalizedLeaves];
|
|
85
|
+
// Track which original leaves each node represents
|
|
86
|
+
// At level 0, each node represents itself
|
|
87
|
+
let nodeToLeaves = new Map();
|
|
88
|
+
normalizedLeaves.forEach(leaf => nodeToLeaves.set(leaf, [leaf]));
|
|
89
|
+
while (currentLevel.length > 1) {
|
|
90
|
+
const nextLevel = [];
|
|
91
|
+
const nextNodeToLeaves = new Map();
|
|
92
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
93
|
+
const left = currentLevel[i];
|
|
94
|
+
const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left; // Duplicate if odd
|
|
95
|
+
const parent = await hashPair(left, right);
|
|
96
|
+
nextLevel.push(parent);
|
|
97
|
+
// Track which leaves this parent represents
|
|
98
|
+
const leftLeaves = nodeToLeaves.get(left) || [];
|
|
99
|
+
const rightLeaves = nodeToLeaves.get(right) || [];
|
|
100
|
+
nextNodeToLeaves.set(parent, [...leftLeaves, ...rightLeaves]);
|
|
101
|
+
// Add proof elements for all leaves on each side
|
|
102
|
+
// Leaves on the left get right sibling, leaves on the right get left sibling
|
|
103
|
+
for (const leafNorm of leftLeaves) {
|
|
104
|
+
const original = normalizedToOriginal.get(leafNorm);
|
|
105
|
+
const proof = proofs.get(original);
|
|
106
|
+
proof.push({ hash: right, position: 'right' });
|
|
107
|
+
}
|
|
108
|
+
// Only add if right != left (not a duplicate)
|
|
109
|
+
if (left !== right) {
|
|
110
|
+
for (const leafNorm of rightLeaves) {
|
|
111
|
+
const original = normalizedToOriginal.get(leafNorm);
|
|
112
|
+
const proof = proofs.get(original);
|
|
113
|
+
proof.push({ hash: left, position: 'left' });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
currentLevel = nextLevel;
|
|
118
|
+
nodeToLeaves = nextNodeToLeaves;
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
root: currentLevel[0],
|
|
122
|
+
proofs,
|
|
123
|
+
leaves
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Verify that a leaf is part of a Merkle tree given its proof
|
|
128
|
+
*
|
|
129
|
+
* @param leaf - The leaf value to verify in Merkle tree
|
|
130
|
+
* @param proof - The Merkle proof path (array of hashes from leaf to root)
|
|
131
|
+
* @param root - The Merkle tree root hash (0x-prefixed hex)
|
|
132
|
+
* @returns True if the proof is valid
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* const isValid = await verifyMerkleProof(
|
|
137
|
+
* 'did:fide:0x00abc...',
|
|
138
|
+
* proof,
|
|
139
|
+
* root
|
|
140
|
+
* );
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export async function verifyMerkleProof(leaf, proof, root) {
|
|
144
|
+
let currentHash = normalizeLeaf(leaf);
|
|
145
|
+
for (const element of proof) {
|
|
146
|
+
if (element.position === 'left') {
|
|
147
|
+
currentHash = await hashPair(element.hash, currentHash);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
currentHash = await hashPair(currentHash, element.hash);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return currentHash === root;
|
|
154
|
+
}
|