@bsv/sdk 1.4.0 → 1.4.1
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/cjs/mod.js +2 -0
- package/dist/cjs/mod.js.map +1 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/identity/IdentityClient.js +258 -0
- package/dist/cjs/src/identity/IdentityClient.js.map +1 -0
- package/dist/cjs/src/identity/index.js +19 -0
- package/dist/cjs/src/identity/index.js.map +1 -0
- package/dist/cjs/src/identity/types/index.js +30 -0
- package/dist/cjs/src/identity/types/index.js.map +1 -0
- package/dist/cjs/src/registry/RegistryClient.js +392 -0
- package/dist/cjs/src/registry/RegistryClient.js.map +1 -0
- package/dist/cjs/src/registry/index.js +19 -0
- package/dist/cjs/src/registry/index.js.map +1 -0
- package/dist/cjs/src/registry/types/index.js +3 -0
- package/dist/cjs/src/registry/types/index.js.map +1 -0
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/mod.js +2 -0
- package/dist/esm/mod.js.map +1 -1
- package/dist/esm/src/identity/IdentityClient.js +255 -0
- package/dist/esm/src/identity/IdentityClient.js.map +1 -0
- package/dist/esm/src/identity/index.js +3 -0
- package/dist/esm/src/identity/index.js.map +1 -0
- package/dist/esm/src/identity/types/index.js +27 -0
- package/dist/esm/src/identity/types/index.js.map +1 -0
- package/dist/esm/src/registry/RegistryClient.js +388 -0
- package/dist/esm/src/registry/RegistryClient.js.map +1 -0
- package/dist/esm/src/registry/index.js +3 -0
- package/dist/esm/src/registry/index.js.map +1 -0
- package/dist/esm/src/registry/types/index.js +2 -0
- package/dist/esm/src/registry/types/index.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/mod.d.ts +2 -0
- package/dist/types/mod.d.ts.map +1 -1
- package/dist/types/src/identity/IdentityClient.d.ts +50 -0
- package/dist/types/src/identity/IdentityClient.d.ts.map +1 -0
- package/dist/types/src/identity/index.d.ts +3 -0
- package/dist/types/src/identity/index.d.ts.map +1 -0
- package/dist/types/src/identity/types/index.d.ts +30 -0
- package/dist/types/src/identity/types/index.d.ts.map +1 -0
- package/dist/types/src/registry/RegistryClient.d.ts +94 -0
- package/dist/types/src/registry/RegistryClient.d.ts.map +1 -0
- package/dist/types/src/registry/index.d.ts +3 -0
- package/dist/types/src/registry/index.d.ts.map +1 -0
- package/dist/types/src/registry/types/index.d.ts +86 -0
- package/dist/types/src/registry/types/index.d.ts.map +1 -0
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/mod.ts +3 -1
- package/package.json +2 -2
- package/src/identity/IdentityClient.ts +305 -0
- package/src/identity/README.md +93 -0
- package/src/identity/__tests/IdentityClient.test.ts +278 -0
- package/src/identity/index.ts +2 -0
- package/src/identity/types/index.ts +46 -0
- package/src/registry/RegistryClient.ts +493 -0
- package/src/registry/__tests/RegistryClient.test.ts +444 -0
- package/src/registry/index.ts +2 -0
- package/src/registry/types/index.ts +101 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { WalletCertificate, WalletInterface } from '../../wallet/index'
|
|
2
|
+
import { IdentityClient } from '../IdentityClient'
|
|
3
|
+
import { Certificate } from '../../auth/certificates/index.js'
|
|
4
|
+
import { KNOWN_IDENTITY_TYPES, defaultIdentity } from '../types/index.js'
|
|
5
|
+
|
|
6
|
+
// ----- Mocks for external dependencies -----
|
|
7
|
+
jest.mock('../../script', () => {
|
|
8
|
+
return {
|
|
9
|
+
PushDrop: jest.fn().mockImplementation(() => ({
|
|
10
|
+
lock: jest.fn().mockResolvedValue({
|
|
11
|
+
toHex: () => 'lockingScriptHex'
|
|
12
|
+
}),
|
|
13
|
+
unlock: jest.fn()
|
|
14
|
+
}))
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
jest.mock('../../overlay-tools/index.js', () => {
|
|
19
|
+
return {
|
|
20
|
+
TopicBroadcaster: jest.fn().mockImplementation(() => ({
|
|
21
|
+
broadcast: jest.fn().mockResolvedValue('broadcastResult')
|
|
22
|
+
}))
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
jest.mock('../../transaction/index.js', () => {
|
|
27
|
+
return {
|
|
28
|
+
Transaction: {
|
|
29
|
+
fromAtomicBEEF: jest.fn().mockImplementation((tx) => ({
|
|
30
|
+
toHexBEEF: () => 'transactionHex'
|
|
31
|
+
})),
|
|
32
|
+
fromBEEF: jest.fn()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// ----- Begin Test Suite -----
|
|
38
|
+
describe('IdentityClient', () => {
|
|
39
|
+
let walletMock: Partial<WalletInterface>
|
|
40
|
+
let identityClient: IdentityClient
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
// Create a fake wallet implementing the methods used by IdentityClient.
|
|
44
|
+
walletMock = {
|
|
45
|
+
proveCertificate: jest.fn().mockResolvedValue({ keyringForVerifier: 'fakeKeyring' }),
|
|
46
|
+
createAction: jest.fn().mockResolvedValue({
|
|
47
|
+
tx: [1, 2, 3],
|
|
48
|
+
signableTransaction: { tx: [1, 2, 3], reference: 'ref' }
|
|
49
|
+
}),
|
|
50
|
+
listCertificates: jest.fn().mockResolvedValue({ certificates: [] }),
|
|
51
|
+
acquireCertificate: jest.fn().mockResolvedValue({
|
|
52
|
+
fields: { name: 'Alice' },
|
|
53
|
+
verify: jest.fn().mockResolvedValue(true)
|
|
54
|
+
}),
|
|
55
|
+
signAction: jest.fn().mockResolvedValue({ tx: [4, 5, 6] }),
|
|
56
|
+
getNetwork: jest.fn().mockResolvedValue({ network: 'testnet' }),
|
|
57
|
+
discoverByIdentityKey: jest.fn(),
|
|
58
|
+
discoverByAttributes: jest.fn()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
identityClient = new IdentityClient(walletMock as WalletInterface)
|
|
62
|
+
|
|
63
|
+
// Clear any previous calls/spies.
|
|
64
|
+
jest.clearAllMocks()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('publiclyRevealAttributes', () => {
|
|
68
|
+
it('should throw an error if certificate has no fields', async () => {
|
|
69
|
+
const certificate = {
|
|
70
|
+
fields: {},
|
|
71
|
+
verify: jest.fn().mockResolvedValue(true)
|
|
72
|
+
} as any as WalletCertificate
|
|
73
|
+
const fieldsToReveal = ['name']
|
|
74
|
+
await expect(
|
|
75
|
+
identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
|
|
76
|
+
).rejects.toThrow('Certificate has no fields to reveal!')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('should throw an error if fieldsToReveal is empty', async () => {
|
|
80
|
+
const certificate = {
|
|
81
|
+
fields: { name: 'Alice' },
|
|
82
|
+
verify: jest.fn().mockResolvedValue(true)
|
|
83
|
+
} as any as WalletCertificate
|
|
84
|
+
const fieldsToReveal: string[] = []
|
|
85
|
+
await expect(
|
|
86
|
+
identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
|
|
87
|
+
).rejects.toThrow('You must reveal at least one field!')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should throw an error if certificate verification fails', async () => {
|
|
91
|
+
const certificate = {
|
|
92
|
+
fields: { name: 'Alice' },
|
|
93
|
+
verify: jest.fn().mockRejectedValue(new Error('Verification error')),
|
|
94
|
+
type: 'dummyType',
|
|
95
|
+
serialNumber: 'dummySerial',
|
|
96
|
+
subject: 'dummySubject',
|
|
97
|
+
certifier: 'dummyCertifier',
|
|
98
|
+
revocationOutpoint: 'dummyRevocation',
|
|
99
|
+
signature: 'dummySignature'
|
|
100
|
+
} as any as WalletCertificate
|
|
101
|
+
const fieldsToReveal = ['name']
|
|
102
|
+
await expect(
|
|
103
|
+
identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
|
|
104
|
+
).rejects.toThrow('Certificate verification failed!')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should publicly reveal attributes successfully', async () => {
|
|
108
|
+
// Prepare a dummy certificate with all required properties.
|
|
109
|
+
const certificate = {
|
|
110
|
+
fields: { name: 'Alice' },
|
|
111
|
+
verify: jest.fn().mockResolvedValue(true), // this property is not used since the Certificate is re-instantiated
|
|
112
|
+
type: 'xCert',
|
|
113
|
+
serialNumber: '12345',
|
|
114
|
+
subject: 'abcdef1234567890',
|
|
115
|
+
certifier: 'CertifierX',
|
|
116
|
+
revocationOutpoint: 'outpoint1',
|
|
117
|
+
signature: 'signature1'
|
|
118
|
+
} as any as WalletCertificate
|
|
119
|
+
|
|
120
|
+
// Ensure that Certificate.verify (called on the re-instantiated Certificate)
|
|
121
|
+
// resolves successfully.
|
|
122
|
+
jest.spyOn(Certificate.prototype, 'verify').mockResolvedValue(false)
|
|
123
|
+
|
|
124
|
+
const fieldsToReveal = ['name']
|
|
125
|
+
const result = await identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
|
|
126
|
+
expect(result).toEqual('broadcastResult')
|
|
127
|
+
|
|
128
|
+
// Validate that proveCertificate was called with the proper arguments.
|
|
129
|
+
expect(walletMock.proveCertificate).toHaveBeenCalledWith({
|
|
130
|
+
certificate,
|
|
131
|
+
fieldsToReveal,
|
|
132
|
+
verifier: expect.any(String)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Validate that createAction was called.
|
|
136
|
+
expect(walletMock.createAction).toHaveBeenCalled()
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('resolveByIdentityKey', () => {
|
|
141
|
+
it('should return parsed identities from discovered certificates', async () => {
|
|
142
|
+
const dummyCertificate = {
|
|
143
|
+
type: KNOWN_IDENTITY_TYPES.xCert,
|
|
144
|
+
subject: 'abcdef1234567890',
|
|
145
|
+
decryptedFields: {
|
|
146
|
+
userName: 'Alice',
|
|
147
|
+
profilePhoto: 'alicePhotoUrl'
|
|
148
|
+
},
|
|
149
|
+
certifierInfo: {
|
|
150
|
+
name: 'CertifierX',
|
|
151
|
+
iconUrl: 'certifierIconUrl'
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Mock discoverByIdentityKey to return a certificate list.
|
|
155
|
+
walletMock.discoverByIdentityKey = jest.fn().mockResolvedValue({ certificates: [dummyCertificate] })
|
|
156
|
+
|
|
157
|
+
const identities = await identityClient.resolveByIdentityKey({ identityKey: 'dummyKey' })
|
|
158
|
+
expect(walletMock.discoverByIdentityKey).toHaveBeenCalledWith({ identityKey: 'dummyKey' }, undefined)
|
|
159
|
+
expect(identities).toHaveLength(1)
|
|
160
|
+
expect(identities[0]).toEqual({
|
|
161
|
+
name: 'Alice',
|
|
162
|
+
avatarURL: 'alicePhotoUrl',
|
|
163
|
+
abbreviatedKey: 'abcdef1234...',
|
|
164
|
+
identityKey: 'abcdef1234567890',
|
|
165
|
+
badgeLabel: 'X account certified by CertifierX',
|
|
166
|
+
badgeIconURL: 'certifierIconUrl',
|
|
167
|
+
badgeClickURL: 'https://socialcert.net'
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should throw if createAction returns no tx', async () => {
|
|
173
|
+
const certificate = {
|
|
174
|
+
fields: { name: 'Alice' },
|
|
175
|
+
verify: jest.fn().mockResolvedValue(true),
|
|
176
|
+
type: 'xCert',
|
|
177
|
+
serialNumber: '12345',
|
|
178
|
+
subject: 'abcdef1234567890',
|
|
179
|
+
certifier: 'CertifierX',
|
|
180
|
+
revocationOutpoint: 'outpoint1',
|
|
181
|
+
signature: 'signature1'
|
|
182
|
+
} as any as WalletCertificate
|
|
183
|
+
|
|
184
|
+
jest.spyOn(Certificate.prototype, 'verify').mockResolvedValue(false)
|
|
185
|
+
|
|
186
|
+
// Simulate createAction returning an object with tx = undefined
|
|
187
|
+
walletMock.createAction = jest.fn().mockResolvedValue({
|
|
188
|
+
tx: undefined,
|
|
189
|
+
signableTransaction: { tx: undefined, reference: 'ref' }
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const fieldsToReveal = ['name']
|
|
193
|
+
|
|
194
|
+
await expect(
|
|
195
|
+
identityClient.publiclyRevealAttributes(certificate, fieldsToReveal)
|
|
196
|
+
).rejects.toThrow('Public reveal failed: failed to create action!')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
describe('resolveByAttributes', () => {
|
|
200
|
+
it('should return parsed identities from discovered certificates', async () => {
|
|
201
|
+
const dummyCertificate = {
|
|
202
|
+
type: KNOWN_IDENTITY_TYPES.emailCert,
|
|
203
|
+
subject: 'emailSubject1234',
|
|
204
|
+
decryptedFields: {
|
|
205
|
+
email: 'alice@example.com',
|
|
206
|
+
profilePhoto: 'ignored' // not used for email type
|
|
207
|
+
},
|
|
208
|
+
certifierInfo: {
|
|
209
|
+
name: 'EmailCertifier',
|
|
210
|
+
iconUrl: 'emailIconUrl'
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Mock discoverByAttributes to return a certificate list.
|
|
214
|
+
walletMock.discoverByAttributes = jest.fn().mockResolvedValue({ certificates: [dummyCertificate] })
|
|
215
|
+
|
|
216
|
+
const identities = await identityClient.resolveByAttributes({ attributes: { email: 'alice@example.com' } })
|
|
217
|
+
expect(walletMock.discoverByAttributes).toHaveBeenCalledWith({ attributes: { email: 'alice@example.com' } }, undefined)
|
|
218
|
+
expect(identities).toHaveLength(1)
|
|
219
|
+
expect(identities[0]).toEqual({
|
|
220
|
+
name: 'alice@example.com',
|
|
221
|
+
avatarURL: 'XUTZxep7BBghAJbSBwTjNfmcsDdRFs5EaGEgkESGSgjJVYgMEizu',
|
|
222
|
+
abbreviatedKey: 'emailSubje...',
|
|
223
|
+
identityKey: 'emailSubject1234',
|
|
224
|
+
badgeLabel: 'Email certified by EmailCertifier',
|
|
225
|
+
badgeIconURL: 'emailIconUrl',
|
|
226
|
+
badgeClickURL: 'https://socialcert.net'
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
describe('parseIdentity', () => {
|
|
232
|
+
it('should correctly parse an xCert identity', () => {
|
|
233
|
+
const dummyCertificate = {
|
|
234
|
+
type: KNOWN_IDENTITY_TYPES.xCert,
|
|
235
|
+
subject: 'abcdef1234567890',
|
|
236
|
+
decryptedFields: {
|
|
237
|
+
userName: 'Alice',
|
|
238
|
+
profilePhoto: 'alicePhotoUrl'
|
|
239
|
+
},
|
|
240
|
+
certifierInfo: {
|
|
241
|
+
name: 'CertifierX',
|
|
242
|
+
iconUrl: 'certifierIconUrl'
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const identity = IdentityClient.parseIdentity(dummyCertificate as unknown as any)
|
|
246
|
+
expect(identity).toEqual({
|
|
247
|
+
name: 'Alice',
|
|
248
|
+
avatarURL: 'alicePhotoUrl',
|
|
249
|
+
abbreviatedKey: 'abcdef1234...',
|
|
250
|
+
identityKey: 'abcdef1234567890',
|
|
251
|
+
badgeLabel: 'X account certified by CertifierX',
|
|
252
|
+
badgeIconURL: 'certifierIconUrl',
|
|
253
|
+
badgeClickURL: 'https://socialcert.net'
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('should return default identity for unknown type', () => {
|
|
258
|
+
const dummyCertificate = {
|
|
259
|
+
type: 'unknownType',
|
|
260
|
+
subject: '',
|
|
261
|
+
decryptedFields: {
|
|
262
|
+
profilePhoto: 'defaultPhoto'
|
|
263
|
+
},
|
|
264
|
+
certifierInfo: {}
|
|
265
|
+
}
|
|
266
|
+
const identity = IdentityClient.parseIdentity(dummyCertificate as any)
|
|
267
|
+
expect(identity).toEqual({
|
|
268
|
+
name: defaultIdentity.name,
|
|
269
|
+
avatarURL: 'defaultPhoto',
|
|
270
|
+
abbreviatedKey: '',
|
|
271
|
+
identityKey: '',
|
|
272
|
+
badgeLabel: defaultIdentity.badgeLabel,
|
|
273
|
+
badgeIconURL: defaultIdentity.badgeIconURL,
|
|
274
|
+
badgeClickURL: defaultIdentity.badgeClickURL
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { WalletProtocol } from "../../wallet/index.js"
|
|
2
|
+
|
|
3
|
+
export const defaultIdentity: DisplayableIdentity = {
|
|
4
|
+
name: 'Unknown Identity',
|
|
5
|
+
avatarURL: 'XUUB8bbn9fEthk15Ge3zTQXypUShfC94vFjp65v7u5CQ8qkpxzst',
|
|
6
|
+
identityKey: '',
|
|
7
|
+
abbreviatedKey: '',
|
|
8
|
+
badgeIconURL: 'XUUV39HVPkpmMzYNTx7rpKzJvXfeiVyQWg2vfSpjBAuhunTCA9uG',
|
|
9
|
+
badgeLabel: 'Not verified by anyone you trust.',
|
|
10
|
+
badgeClickURL: 'https://projectbabbage.com/docs/unknown-identity'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface IdentityClientOptions {
|
|
14
|
+
protocolID: WalletProtocol
|
|
15
|
+
keyID: string
|
|
16
|
+
tokenAmount: number
|
|
17
|
+
outputIndex: number
|
|
18
|
+
}
|
|
19
|
+
export const DEFAULT_IDENTITY_CLIENT_OPTIONS: IdentityClientOptions = {
|
|
20
|
+
protocolID: [1, 'identity'],
|
|
21
|
+
keyID: '1',
|
|
22
|
+
tokenAmount: 1,
|
|
23
|
+
outputIndex: 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DisplayableIdentity {
|
|
27
|
+
name: string
|
|
28
|
+
avatarURL: string
|
|
29
|
+
abbreviatedKey: string
|
|
30
|
+
identityKey: string
|
|
31
|
+
badgeIconURL: string
|
|
32
|
+
badgeLabel: string
|
|
33
|
+
badgeClickURL: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const KNOWN_IDENTITY_TYPES = {
|
|
37
|
+
identiCert: 'z40BOInXkI8m7f/wBrv4MJ09bZfzZbTj2fJqCtONqCY=',
|
|
38
|
+
discordCert: '2TgqRC35B1zehGmB21xveZNc7i5iqHc0uxMb+1NMPW4=',
|
|
39
|
+
phoneCert: 'mffUklUzxbHr65xLohn0hRL0Tq2GjW1GYF/OPfzqJ6A=',
|
|
40
|
+
xCert: 'vdDWvftf1H+5+ZprUw123kjHlywH+v20aPQTuXgMpNc=',
|
|
41
|
+
registrant: 'YoPsbfR6YQczjzPdHCoGC7nJsOdPQR50+SYqcWpJ0y0=',
|
|
42
|
+
emailCert: 'exOl3KM0dIJ04EW5pZgbZmPag6MdJXd3/a1enmUU/BA=',
|
|
43
|
+
anyone: 'mfkOMfLDQmrr3SBxBQ5WeE+6Hy3VJRFq6w4A5Ljtlis=',
|
|
44
|
+
self: 'Hkge6X5JRxt1cWXtHLCrSTg6dCVTxjQJJ48iOYd7n3g=',
|
|
45
|
+
coolCert: 'AGfk/WrT1eBDXpz3mcw386Zww2HmqcIn3uY6x4Af1eo='
|
|
46
|
+
}
|