Skip to content

Commit 606b22d

Browse files
committed
feat: jwt sigs in w3id
1 parent 96e842e commit 606b22d

File tree

4 files changed

+236
-122
lines changed

4 files changed

+236
-122
lines changed

infrastructure/w3id/src/index.ts

Lines changed: 121 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,140 @@
11
import { IDLogManager } from "./logs/log-manager";
2-
import type { LogEvent, Signer } from "./logs/log.types";
2+
import type { LogEvent, Signer, JWTPayload, JWTHeader } from "./logs/log.types";
33
import type { StorageSpec } from "./logs/storage/storage-spec";
44
import { generateRandomAlphaNum } from "./utils/rand";
55
import { v4 as uuidv4 } from "uuid";
66
import { generateUuid } from "./utils/uuid";
7+
import { signJWT } from "./utils/jwt";
78

89
export class W3ID {
9-
constructor(
10-
public id: string,
11-
public logs?: IDLogManager,
12-
) {}
10+
constructor(
11+
public id: string,
12+
public logs?: IDLogManager,
13+
) {}
14+
15+
/**
16+
* Signs a JWT with the W3ID's signer
17+
* @param payload - The JWT payload
18+
* @param header - Optional JWT header (defaults to using the signer's alg and W3ID's id as kid)
19+
* @returns The signed JWT
20+
*/
21+
public async signJWT(
22+
payload: JWTPayload,
23+
header?: JWTHeader,
24+
): Promise<string> {
25+
if (!this.logs?.signer) {
26+
throw new Error("W3ID must have a signer to sign JWTs");
27+
}
28+
return signJWT(this.logs.signer, payload, `${this.id}#0`, header);
29+
}
1330
}
1431

1532
export class W3IDBuilder {
16-
private signer?: Signer;
17-
private repository?: StorageSpec<LogEvent, LogEvent>;
18-
private entropy?: string;
19-
private namespace?: string;
20-
private nextKeyHash?: string;
21-
private global?: boolean = false;
33+
private signer?: Signer;
34+
private repository?: StorageSpec<LogEvent, LogEvent>;
35+
private entropy?: string;
36+
private namespace?: string;
37+
private nextKeyHash?: string;
38+
private global?: boolean = false;
2239

23-
/**
24-
* Specify entropy to create the identity with
25-
*
26-
* @param {string} str
27-
*/
28-
public withEntropy(str: string): W3IDBuilder {
29-
this.entropy = str;
30-
return this;
31-
}
40+
/**
41+
* Specify entropy to create the identity with
42+
*
43+
* @param {string} str
44+
*/
45+
public withEntropy(str: string): W3IDBuilder {
46+
this.entropy = str;
47+
return this;
48+
}
3249

33-
/**
34-
* Specify namespace to use to generate the UUIDv5
35-
*
36-
* @param {string} uuid
37-
*/
38-
public withNamespace(uuid: string): W3IDBuilder {
39-
this.namespace = uuid;
40-
return this;
41-
}
50+
/**
51+
* Specify namespace to use to generate the UUIDv5
52+
*
53+
* @param {string} uuid
54+
*/
55+
public withNamespace(uuid: string): W3IDBuilder {
56+
this.namespace = uuid;
57+
return this;
58+
}
4259

43-
/**
44-
* Specify whether to create a global identifier or a local identifer
45-
*
46-
* According to the project specification there are supposed to be 2 main types of
47-
* W3ID's ones which are tied to more permanent entities
48-
*
49-
* A global identifer is expected to live at the registry and starts with an \`@\`
50-
*
51-
* @param {boolean} isGlobal
52-
*/
53-
public withGlobal(isGlobal: boolean): W3IDBuilder {
54-
this.global = isGlobal;
55-
return this;
56-
}
60+
/**
61+
* Specify whether to create a global identifier or a local identifer
62+
*
63+
* According to the project specification there are supposed to be 2 main types of
64+
* W3ID's ones which are tied to more permanent entities
65+
*
66+
* A global identifer is expected to live at the registry and starts with an \`@\`
67+
*
68+
* @param {boolean} isGlobal
69+
*/
70+
public withGlobal(isGlobal: boolean): W3IDBuilder {
71+
this.global = isGlobal;
72+
return this;
73+
}
5774

58-
/**
59-
* Add a logs repository to the W3ID, a rotateble key attached W3ID would need a
60-
* repository in which the logs would be stored
61-
*
62-
* @param {StorageSpec<LogEvent, LogEvent>} storage
63-
*/
64-
public withRepository(storage: StorageSpec<LogEvent, LogEvent>): W3IDBuilder {
65-
this.repository = storage;
66-
return this;
67-
}
75+
/**
76+
* Add a logs repository to the W3ID, a rotateble key attached W3ID would need a
77+
* repository in which the logs would be stored
78+
*
79+
* @param {StorageSpec<LogEvent, LogEvent>} storage
80+
*/
81+
public withRepository(
82+
storage: StorageSpec<LogEvent, LogEvent>,
83+
): W3IDBuilder {
84+
this.repository = storage;
85+
return this;
86+
}
6887

69-
/**
70-
* Attach a keypair to the W3ID, a key attached W3ID would also need a repository
71-
* to be added.
72-
*
73-
* @param {Signer} signer
74-
*/
75-
public withSigner(signer: Signer): W3IDBuilder {
76-
this.signer = signer;
77-
return this;
78-
}
88+
/**
89+
* Attach a keypair to the W3ID, a key attached W3ID would also need a repository
90+
* to be added.
91+
*
92+
* @param {Signer} signer
93+
*/
94+
public withSigner(signer: Signer): W3IDBuilder {
95+
this.signer = signer;
96+
return this;
97+
}
7998

80-
/**
81-
* Specify the SHA256 hash of the next key which will sign the next log entry after
82-
* rotation of keys
83-
*
84-
* @param {string} hash
85-
*/
86-
public withNextKeyHash(hash: string): W3IDBuilder {
87-
this.nextKeyHash = hash;
88-
return this;
89-
}
99+
/**
100+
* Specify the SHA256 hash of the next key which will sign the next log entry after
101+
* rotation of keys
102+
*
103+
* @param {string} hash
104+
*/
105+
public withNextKeyHash(hash: string): W3IDBuilder {
106+
this.nextKeyHash = hash;
107+
return this;
108+
}
90109

91-
/**
92-
* Build the W3ID with provided builder options
93-
*
94-
* @returns Promise<W3ID>
95-
*/
96-
public async build(): Promise<W3ID> {
97-
this.entropy = this.entropy ?? generateRandomAlphaNum();
98-
this.namespace = this.namespace ?? uuidv4();
99-
const id = `${
100-
this.global ? "@" : ""
101-
}${generateUuid(this.entropy, this.namespace)}`;
102-
if (!this.signer) {
103-
return new W3ID(id);
104-
}
105-
if (!this.repository)
106-
throw new Error(
107-
"Repository is required, pass with `withRepository` method",
108-
);
110+
/**
111+
* Build the W3ID with provided builder options
112+
*
113+
* @returns Promise<W3ID>
114+
*/
115+
public async build(): Promise<W3ID> {
116+
this.entropy = this.entropy ?? generateRandomAlphaNum();
117+
this.namespace = this.namespace ?? uuidv4();
118+
const id = `${
119+
this.global ? "@" : ""
120+
}${generateUuid(this.entropy, this.namespace)}`;
121+
if (!this.signer) {
122+
return new W3ID(id);
123+
}
124+
if (!this.repository)
125+
throw new Error(
126+
"Repository is required, pass with `withRepository` method",
127+
);
109128

110-
if (!this.nextKeyHash)
111-
throw new Error(
112-
"NextKeyHash is required pass with `withNextKeyHash` method",
113-
);
114-
const logs = new IDLogManager(this.repository, this.signer);
115-
await logs.createLogEvent({
116-
id,
117-
nextKeyHashes: [this.nextKeyHash],
118-
});
119-
return new W3ID(id, logs);
120-
}
129+
if (!this.nextKeyHash)
130+
throw new Error(
131+
"NextKeyHash is required pass with `withNextKeyHash` method",
132+
);
133+
const logs = new IDLogManager(this.repository, this.signer);
134+
await logs.createLogEvent({
135+
id,
136+
nextKeyHashes: [this.nextKeyHash],
137+
});
138+
return new W3ID(id, logs);
139+
}
121140
}
Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,61 @@
11
export type LogEvent = {
2-
id: string;
3-
versionId: string;
4-
versionTime: Date;
5-
updateKeys: string[];
6-
nextKeyHashes: string[];
7-
method: `w3id:v${string}`;
8-
proof?: string;
2+
id: string;
3+
versionId: string;
4+
versionTime: Date;
5+
updateKeys: string[];
6+
nextKeyHashes: string[];
7+
method: `w3id:v${string}`;
8+
proof?: string;
99
};
1010

1111
export type VerifierCallback = (
12-
message: string,
13-
signature: string,
14-
pubKey: string,
12+
message: string,
13+
signature: string,
14+
pubKey: string,
1515
) => Promise<boolean>;
1616

17+
export type JWTHeader = {
18+
alg: string;
19+
typ: "JWT";
20+
kid?: string;
21+
};
22+
23+
export type JWTPayload = {
24+
[key: string]: any;
25+
iat?: number;
26+
exp?: number;
27+
nbf?: number;
28+
iss?: string;
29+
sub?: string;
30+
aud?: string;
31+
jti?: string;
32+
};
33+
1734
export type Signer = {
18-
sign: (message: string) => Promise<string> | string;
19-
pubKey: string;
35+
sign: (message: string) => Promise<string> | string;
36+
pubKey: string;
37+
alg: string;
2038
};
2139

2240
export type RotationLogOptions = {
23-
nextKeyHashes: string[];
24-
nextKeySigner: Signer;
41+
nextKeyHashes: string[];
42+
nextKeySigner: Signer;
2543
};
2644

2745
export type GenesisLogOptions = {
28-
nextKeyHashes: string[];
29-
id: string;
46+
nextKeyHashes: string[];
47+
id: string;
3048
};
3149

3250
export function isGenesisOptions(
33-
options: CreateLogEventOptions,
51+
options: CreateLogEventOptions,
3452
): options is GenesisLogOptions {
35-
return "id" in options;
53+
return "id" in options;
3654
}
3755
export function isRotationOptions(
38-
options: CreateLogEventOptions,
56+
options: CreateLogEventOptions,
3957
): options is RotationLogOptions {
40-
return "nextKeySigner" in options;
58+
return "nextKeySigner" in options;
4159
}
4260

4361
export type CreateLogEventOptions = GenesisLogOptions | RotationLogOptions;

infrastructure/w3id/tests/utils/crypto.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const verifierCallback: VerifierCallback = async (
2727
export function createSigner(keyPair: nacl.SignKeyPair): Signer {
2828
const publicKey = uint8ArrayToHex(keyPair.publicKey);
2929
const signer: Signer = {
30+
alg: "ed25519",
3031
pubKey: publicKey,
3132
sign: (str: string) => {
3233
const buffer = stringToUint8Array(str);

0 commit comments

Comments
 (0)