1
+ import { Sha256 , utils } from '@web5/crypto' ;
2
+ import { concatenateUrl } from './utils.js' ;
3
+ import { Convert } from '@web5/common' ;
4
+
5
+ /**
6
+ * A client for registering tenants with a DWN.
7
+ */
8
+ export class DwnRegistrar {
9
+ /**
10
+ * Registers a new tenant with the given DWN.
11
+ * NOTE: Assumes the user has already accepted the terms of service.
12
+ * NOTE: Currently the DWN Server from `dwn-server` does not require user signature.
13
+ * TODO: bring in types from `dwn-server`.
14
+ */
15
+ public static async registerTenant ( dwnEndpoint : string , did : string ) : Promise < void > {
16
+
17
+ const registrationEndpoint = concatenateUrl ( dwnEndpoint , 'registration' ) ;
18
+ const termsOfUseEndpoint = concatenateUrl ( registrationEndpoint , 'terms-of-service' ) ;
19
+ const proofOfWorkEndpoint = concatenateUrl ( registrationEndpoint , 'proof-of-work' ) ;
20
+
21
+ // fetch the terms-of-service
22
+ const termsOfServiceGetResponse = await fetch ( termsOfUseEndpoint , {
23
+ method : 'GET' ,
24
+ } ) ;
25
+
26
+ if ( termsOfServiceGetResponse . status !== 200 ) {
27
+ const statusCode = termsOfServiceGetResponse . status ;
28
+ const statusText = termsOfServiceGetResponse . statusText ;
29
+ const errorText = await termsOfServiceGetResponse . text ( ) ;
30
+ throw new Error ( `Failed fetching terms-of-service: ${ statusCode } ${ statusText } : ${ errorText } ` ) ;
31
+ }
32
+ const termsOfServiceFetched = await termsOfServiceGetResponse . text ( ) ;
33
+
34
+ // fetch the proof-of-work challenge
35
+ const proofOfWorkChallengeGetResponse = await fetch ( proofOfWorkEndpoint , {
36
+ method : 'GET' ,
37
+ } ) ;
38
+ const { challengeNonce, maximumAllowedHashValue} = await proofOfWorkChallengeGetResponse . json ( ) ;
39
+
40
+ // create registration data based on the hash of the terms-of-service and the DID
41
+ const registrationData = {
42
+ did,
43
+ termsOfServiceHash : await DwnRegistrar . hashAsHexString ( termsOfServiceFetched ) ,
44
+ } ;
45
+
46
+ // compute the proof-of-work response nonce based on the the proof-of-work challenge and the registration data.
47
+ const responseNonce = await DwnRegistrar . findQualifiedResponseNonce ( {
48
+ challengeNonce,
49
+ maximumAllowedHashValue,
50
+ requestData : JSON . stringify ( registrationData ) ,
51
+ } ) ;
52
+
53
+ // send the registration request to the server
54
+ const registrationRequest = {
55
+ registrationData,
56
+ proofOfWork : {
57
+ challengeNonce,
58
+ responseNonce,
59
+ } ,
60
+ } ;
61
+
62
+ const registrationResponse = await fetch ( registrationEndpoint , {
63
+ method : 'POST' ,
64
+ headers : { 'Content-Type' : 'application/json' } ,
65
+ body : JSON . stringify ( registrationRequest ) ,
66
+ } ) ;
67
+
68
+ if ( registrationResponse . status !== 200 ) {
69
+ const statusCode = registrationResponse . status ;
70
+ const statusText = registrationResponse . statusText ;
71
+ const errorText = await registrationResponse . text ( ) ;
72
+ throw new Error ( `Registration failed: ${ statusCode } ${ statusText } : ${ errorText } ` ) ;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Computes the SHA-256 hash of the given array of strings.
78
+ */
79
+ public static async hashAsHexString ( input : string ) : Promise < string > {
80
+ const hashAsBytes = await Sha256 . digest ( { data : Convert . string ( input ) . toUint8Array ( ) } ) ;
81
+ const hashAsHex = Convert . uint8Array ( hashAsBytes ) . toHex ( ) ;
82
+ return hashAsHex ;
83
+ }
84
+
85
+ /**
86
+ * Finds a response nonce that qualifies the difficulty requirement for the given proof-of-work challenge and request data.
87
+ */
88
+ public static async findQualifiedResponseNonce ( input : {
89
+ maximumAllowedHashValue : string ;
90
+ challengeNonce : string ;
91
+ requestData : string ;
92
+ } ) : Promise < string > {
93
+ const startTime = Date . now ( ) ;
94
+
95
+ const { maximumAllowedHashValue, challengeNonce, requestData } = input ;
96
+ const maximumAllowedHashValueAsBigInt = BigInt ( `0x${ maximumAllowedHashValue } ` ) ;
97
+
98
+ let iterations = 1 ;
99
+ let responseNonce ;
100
+ let qualifiedSolutionNonceFound = false ;
101
+ do {
102
+ responseNonce = await this . generateNonce ( ) ;
103
+ const computedHash = await DwnRegistrar . hashAsHexString ( challengeNonce + responseNonce + requestData ) ;
104
+ const computedHashAsBigInt = BigInt ( `0x${ computedHash } ` ) ;
105
+
106
+ qualifiedSolutionNonceFound = computedHashAsBigInt <= maximumAllowedHashValueAsBigInt ;
107
+
108
+ iterations ++ ;
109
+ } while ( ! qualifiedSolutionNonceFound ) ;
110
+
111
+ // Log final/successful iteration.
112
+ console . log (
113
+ `iterations: ${ iterations } , time lapsed: ${ Date . now ( ) - startTime } ms` ,
114
+ ) ;
115
+
116
+ return responseNonce ;
117
+ }
118
+
119
+ /**
120
+ * Generates 32 random bytes expressed as a HEX string.
121
+ */
122
+ public static async generateNonce ( ) : Promise < string > {
123
+ const randomBytes = utils . randomBytes ( 32 ) ;
124
+ const hexString = await Convert . uint8Array ( randomBytes ) . toHex ( ) . toUpperCase ( ) ;
125
+ return hexString ;
126
+ }
127
+ }
0 commit comments