did-beta-bch 1.0.1 → 1.0.2
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/credentials.d.ts +21 -0
- package/dist/credentials.js +78 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +7 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/verify.d.ts +6 -1
- package/dist/verify.js +35 -2
- package/package.json +1 -1
package/dist/credentials.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface CredentialPayload {
|
|
|
9
9
|
subject: string;
|
|
10
10
|
type: string;
|
|
11
11
|
issuedAt: number;
|
|
12
|
+
rating?: 1 | 2 | 3 | 4 | 5;
|
|
13
|
+
comment?: string;
|
|
12
14
|
expiry?: number;
|
|
13
15
|
claims?: Record<string, string>;
|
|
14
16
|
}
|
|
@@ -25,3 +27,22 @@ export declare function issueCredential(subjectNpub: string, type: string, issue
|
|
|
25
27
|
}>;
|
|
26
28
|
/** Resolve all credentials issued to an npub */
|
|
27
29
|
export declare function resolveCredentials(npub: string, relays?: string[]): Promise<IssuedCredential[]>;
|
|
30
|
+
/** Issue a star rating (1–5) to an npub — usable by any wallet or app */
|
|
31
|
+
export declare function issueRating(subjectNpub: string, rating: 1 | 2 | 3 | 4 | 5, issuerPrivkey: Uint8Array, opts?: {
|
|
32
|
+
comment?: string;
|
|
33
|
+
txid?: string;
|
|
34
|
+
network?: string;
|
|
35
|
+
}, relays?: string[]): Promise<{
|
|
36
|
+
credential: IssuedCredential;
|
|
37
|
+
results: RelayResult[];
|
|
38
|
+
}>;
|
|
39
|
+
/** Revoke a credential by publishing a NIP-09 kind:5 deletion event */
|
|
40
|
+
export declare function revokeCredential(eventId: string, issuerPrivkey: Uint8Array, relays?: string[]): Promise<RelayResult[]>;
|
|
41
|
+
/** Compute a reputation score for an npub based on ratings and credentials */
|
|
42
|
+
export declare function scoreReputation(npub: string, relays?: string[]): Promise<{
|
|
43
|
+
credentialCount: number;
|
|
44
|
+
ratingCount: number;
|
|
45
|
+
averageRating: number | null;
|
|
46
|
+
uniqueIssuers: number;
|
|
47
|
+
score: number;
|
|
48
|
+
}>;
|
package/dist/credentials.js
CHANGED
|
@@ -49,7 +49,16 @@ export async function resolveCredentials(npub, relays = DEFAULT_RELAYS) {
|
|
|
49
49
|
"#t": ["cashlet-did-credential"],
|
|
50
50
|
limit: 50,
|
|
51
51
|
});
|
|
52
|
-
|
|
52
|
+
// Fetch revocations and filter them out
|
|
53
|
+
const revocations = await queryRelays(relays, {
|
|
54
|
+
kinds: [5],
|
|
55
|
+
authors: events.map(e => e.pubkey),
|
|
56
|
+
limit: 100,
|
|
57
|
+
});
|
|
58
|
+
const revokedIds = new Set(revocations.flatMap(e => e.tags.filter((t) => t[0] === "e").map((t) => t[1])));
|
|
59
|
+
return events
|
|
60
|
+
.filter(e => !revokedIds.has(e.id))
|
|
61
|
+
.map(e => {
|
|
53
62
|
try {
|
|
54
63
|
const payload = JSON.parse(e.content);
|
|
55
64
|
return { id: e.id, issuer: e.pubkey, payload, rawEvent: e };
|
|
@@ -59,3 +68,71 @@ export async function resolveCredentials(npub, relays = DEFAULT_RELAYS) {
|
|
|
59
68
|
}
|
|
60
69
|
}).filter(Boolean);
|
|
61
70
|
}
|
|
71
|
+
/** Issue a star rating (1–5) to an npub — usable by any wallet or app */
|
|
72
|
+
export async function issueRating(subjectNpub, rating, issuerPrivkey, opts = {}, relays = DEFAULT_RELAYS) {
|
|
73
|
+
const claims = {};
|
|
74
|
+
if (opts.txid)
|
|
75
|
+
claims.txid = opts.txid;
|
|
76
|
+
if (opts.network)
|
|
77
|
+
claims.network = opts.network;
|
|
78
|
+
const decoded = nip19.decode(subjectNpub);
|
|
79
|
+
if (decoded.type !== "npub")
|
|
80
|
+
throw new Error("Invalid npub");
|
|
81
|
+
const subjectPubkey = decoded.data;
|
|
82
|
+
const payload = {
|
|
83
|
+
subject: subjectPubkey,
|
|
84
|
+
type: "rating",
|
|
85
|
+
issuedAt: Math.floor(Date.now() / 1000),
|
|
86
|
+
rating,
|
|
87
|
+
...(opts.comment ? { comment: opts.comment } : {}),
|
|
88
|
+
...(Object.keys(claims).length ? { claims } : {}),
|
|
89
|
+
};
|
|
90
|
+
const template = {
|
|
91
|
+
kind: CREDENTIAL_KIND,
|
|
92
|
+
created_at: payload.issuedAt,
|
|
93
|
+
tags: [
|
|
94
|
+
["p", subjectPubkey],
|
|
95
|
+
["credential-type", "rating"],
|
|
96
|
+
["rating", String(rating)],
|
|
97
|
+
["t", "cashlet-did-credential"],
|
|
98
|
+
],
|
|
99
|
+
content: JSON.stringify(payload),
|
|
100
|
+
};
|
|
101
|
+
const signed = finalizeEvent(template, issuerPrivkey);
|
|
102
|
+
const results = await publishToRelays(relays, signed);
|
|
103
|
+
const { getPublicKey } = await import("nostr-tools");
|
|
104
|
+
return {
|
|
105
|
+
credential: { id: signed.id, issuer: getPublicKey(issuerPrivkey), payload, rawEvent: signed },
|
|
106
|
+
results,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Revoke a credential by publishing a NIP-09 kind:5 deletion event */
|
|
110
|
+
export async function revokeCredential(eventId, issuerPrivkey, relays = DEFAULT_RELAYS) {
|
|
111
|
+
const template = {
|
|
112
|
+
kind: 5,
|
|
113
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
114
|
+
tags: [["e", eventId]],
|
|
115
|
+
content: "revoked",
|
|
116
|
+
};
|
|
117
|
+
const signed = finalizeEvent(template, issuerPrivkey);
|
|
118
|
+
return publishToRelays(relays, signed);
|
|
119
|
+
}
|
|
120
|
+
/** Compute a reputation score for an npub based on ratings and credentials */
|
|
121
|
+
export async function scoreReputation(npub, relays = DEFAULT_RELAYS) {
|
|
122
|
+
const credentials = await resolveCredentials(npub, relays);
|
|
123
|
+
const ratings = credentials.filter(c => c.payload.type === "rating" && c.payload.rating);
|
|
124
|
+
const uniqueIssuers = new Set(credentials.map(c => c.issuer)).size;
|
|
125
|
+
const averageRating = ratings.length
|
|
126
|
+
? ratings.reduce((sum, c) => sum + (c.payload.rating ?? 0), 0) / ratings.length
|
|
127
|
+
: null;
|
|
128
|
+
// Score: avg rating (0-50) + unique issuer bonus (0-50, caps at 10 issuers)
|
|
129
|
+
const ratingScore = averageRating ? (averageRating / 5) * 50 : 0;
|
|
130
|
+
const issuerScore = Math.min(uniqueIssuers, 10) * 5;
|
|
131
|
+
return {
|
|
132
|
+
credentialCount: credentials.length,
|
|
133
|
+
ratingCount: ratings.length,
|
|
134
|
+
averageRating: averageRating ? Math.round(averageRating * 10) / 10 : null,
|
|
135
|
+
uniqueIssuers,
|
|
136
|
+
score: Math.round(ratingScore + issuerScore),
|
|
137
|
+
};
|
|
138
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type CashletDidErrorCode = "NOT_FOUND" | "INVALID_SIG" | "INVALID_BINDING" | "RELAY_TIMEOUT" | "RELAY_ERROR" | "INVALID_NPub" | "CHAIN_BROKEN" | "INVALID_NIP05";
|
|
2
|
+
export declare class CashletDidError extends Error {
|
|
3
|
+
readonly code: CashletDidErrorCode;
|
|
4
|
+
constructor(code: CashletDidErrorCode, message: string);
|
|
5
|
+
}
|
package/dist/errors.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { buildDisclosure, verifyDisclosure, type Disclosure, } from "./disclosure.js";
|
|
2
2
|
export { buildAnchorTx, ANCHOR_PREFIX, type AnchorUtxo, type AnchorResult, } from "./anchor.js";
|
|
3
|
-
export { issueCredential, resolveCredentials, CREDENTIAL_KIND, type CredentialPayload, type IssuedCredential, } from "./credentials.js";
|
|
3
|
+
export { issueCredential, issueRating, revokeCredential, resolveCredentials, scoreReputation, CREDENTIAL_KIND, type CredentialPayload, type IssuedCredential, } from "./credentials.js";
|
|
4
4
|
export { PROTOCOL_ID, PROTOCOL_VERSION, BINDING_KIND, type Network, type CanonicalBinding, type IdentityBinding, type NostrEvent, buildBinding, bchSigningPayload, nostrEventTemplate, canonicalize, sha256hex, } from "./binding.js";
|
|
5
5
|
export { publishToRelay, publishToRelays, queryRelay, queryRelays, DEFAULT_RELAYS, type RelayResult, } from "./relay.js";
|
|
6
|
-
export { resolveIdentity, resolveAddress, verifyIdentity, verifyChallengeProof, resolveIdentityChain, type VerifyResult, type VerifyStatus, type ResolvedIdentity, } from "./verify.js";
|
|
6
|
+
export { resolveIdentity, resolveAddress, resolveByNip05, verifyIdentity, verifyChallengeProof, resolveIdentityChain, type VerifyResult, type VerifyStatus, type ResolvedIdentity, } from "./verify.js";
|
|
7
|
+
export { CashletDidError, type CashletDidErrorCode } from "./errors.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { buildDisclosure, verifyDisclosure, } from "./disclosure.js";
|
|
2
2
|
export { buildAnchorTx, ANCHOR_PREFIX, } from "./anchor.js";
|
|
3
|
-
export { issueCredential, resolveCredentials, CREDENTIAL_KIND, } from "./credentials.js";
|
|
3
|
+
export { issueCredential, issueRating, revokeCredential, resolveCredentials, scoreReputation, CREDENTIAL_KIND, } from "./credentials.js";
|
|
4
4
|
export {
|
|
5
5
|
// Protocol constants
|
|
6
6
|
PROTOCOL_ID, PROTOCOL_VERSION, BINDING_KIND,
|
|
@@ -11,4 +11,5 @@ export {
|
|
|
11
11
|
publishToRelay, publishToRelays, queryRelay, queryRelays, DEFAULT_RELAYS, } from "./relay.js";
|
|
12
12
|
export {
|
|
13
13
|
// Verification
|
|
14
|
-
resolveIdentity, resolveAddress, verifyIdentity, verifyChallengeProof, resolveIdentityChain, } from "./verify.js";
|
|
14
|
+
resolveIdentity, resolveAddress, resolveByNip05, verifyIdentity, verifyChallengeProof, resolveIdentityChain, } from "./verify.js";
|
|
15
|
+
export { CashletDidError } from "./errors.js";
|
package/dist/verify.d.ts
CHANGED
|
@@ -35,6 +35,11 @@ export declare function verifyChallengeProof(proof: {
|
|
|
35
35
|
* if (address) wallet.sendTo(address, amount)
|
|
36
36
|
*/
|
|
37
37
|
export declare function resolveAddress(npub: string, relays?: string[]): Promise<string | null>;
|
|
38
|
-
/** Returns full binding history for an npub, newest first */
|
|
38
|
+
/** Returns full binding history for an npub, newest first, with cryptographic supersedes validation */
|
|
39
39
|
export declare function resolveIdentityChain(npub: string, relays?: string[]): Promise<ResolvedIdentity[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a BCH address from a NIP-05 handle (e.g. "alice@cashlet.app")
|
|
42
|
+
* Chains: NIP-05 → npub → cashlet-did binding → BCH address
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveByNip05(handle: string, relays?: string[]): Promise<string | null>;
|
|
40
45
|
export { bchSigningPayload, canonicalize, sha256hex, type CanonicalBinding, type IdentityBinding };
|
package/dist/verify.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { verifyEvent } from "nostr-tools";
|
|
1
|
+
import { verifyEvent, nip05 } from "nostr-tools";
|
|
2
2
|
import { secp256k1, hash256, encodeCashAddress, hash160 } from "@bitauth/libauth";
|
|
3
3
|
import { canonicalize, sha256hex, bchSigningPayload, BINDING_KIND, PROTOCOL_ID, } from "./binding.js";
|
|
4
4
|
import { queryRelays, DEFAULT_RELAYS } from "./relay.js";
|
|
5
|
+
import { CashletDidError } from "./errors.js";
|
|
5
6
|
import { nip19 } from "nostr-tools";
|
|
6
7
|
function bitcoinMsgHash(message) {
|
|
7
8
|
const enc = new TextEncoder();
|
|
@@ -133,7 +134,7 @@ export async function resolveAddress(npub, relays = DEFAULT_RELAYS) {
|
|
|
133
134
|
const identity = await resolveIdentity(npub, relays);
|
|
134
135
|
return identity?.bchAddress ?? null;
|
|
135
136
|
}
|
|
136
|
-
/** Returns full binding history for an npub, newest first */
|
|
137
|
+
/** Returns full binding history for an npub, newest first, with cryptographic supersedes validation */
|
|
137
138
|
export async function resolveIdentityChain(npub, relays = DEFAULT_RELAYS) {
|
|
138
139
|
const decoded = nip19.decode(npub);
|
|
139
140
|
if (decoded.type !== "npub")
|
|
@@ -157,8 +158,40 @@ export async function resolveIdentityChain(npub, relays = DEFAULT_RELAYS) {
|
|
|
157
158
|
createdAt = JSON.parse(event.content).createdAt ?? createdAt;
|
|
158
159
|
}
|
|
159
160
|
catch { /* use event ts */ }
|
|
161
|
+
// Validate supersedes tag cryptographically
|
|
162
|
+
const supersedesTag = event.tags.find(t => t[0] === "supersedes");
|
|
163
|
+
if (supersedesTag) {
|
|
164
|
+
const claimedId = supersedesTag[1];
|
|
165
|
+
// Fetch the referenced event and verify its bindingId matches
|
|
166
|
+
const [prev] = await queryRelays(relays, { ids: [claimedId], limit: 1 });
|
|
167
|
+
if (prev) {
|
|
168
|
+
try {
|
|
169
|
+
const prevContent = JSON.parse(prev.content);
|
|
170
|
+
const actualId = await sha256hex(canonicalize(prevContent));
|
|
171
|
+
if (actualId !== claimedId) {
|
|
172
|
+
throw new CashletDidError("CHAIN_BROKEN", `supersedes tag ${claimedId} does not match referenced event content hash`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
if (e instanceof CashletDidError)
|
|
177
|
+
throw e;
|
|
178
|
+
// content not canonical JSON — skip validation but keep event
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
160
182
|
results.push({ npub, pubkey, bchAddress: bchTag[1], network: bchTag[2] ?? "mainnet", bindingId: dTag[1], createdAt, bchProofTag, rawEvent: event });
|
|
161
183
|
}
|
|
162
184
|
return results;
|
|
163
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolve a BCH address from a NIP-05 handle (e.g. "alice@cashlet.app")
|
|
188
|
+
* Chains: NIP-05 → npub → cashlet-did binding → BCH address
|
|
189
|
+
*/
|
|
190
|
+
export async function resolveByNip05(handle, relays = DEFAULT_RELAYS) {
|
|
191
|
+
const profile = await nip05.queryProfile(handle);
|
|
192
|
+
if (!profile?.pubkey)
|
|
193
|
+
throw new CashletDidError("INVALID_NIP05", `NIP-05 handle not found: ${handle}`);
|
|
194
|
+
const npub = nip19.npubEncode(profile.pubkey);
|
|
195
|
+
return resolveAddress(npub, relays);
|
|
196
|
+
}
|
|
164
197
|
export { bchSigningPayload, canonicalize, sha256hex };
|