@bsv/sdk 1.3.5 → 1.3.7
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/package.json +1 -1
- package/dist/cjs/src/auth/Peer.js +1 -1
- package/dist/cjs/src/auth/Peer.js.map +1 -1
- package/dist/cjs/src/auth/certificates/Certificate.js +25 -6
- package/dist/cjs/src/auth/certificates/Certificate.js.map +1 -1
- package/dist/cjs/src/auth/certificates/MasterCertificate.js +99 -17
- package/dist/cjs/src/auth/certificates/MasterCertificate.js.map +1 -1
- package/dist/cjs/src/auth/certificates/VerifiableCertificate.js +3 -4
- package/dist/cjs/src/auth/certificates/VerifiableCertificate.js.map +1 -1
- package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js +80 -0
- package/dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -0
- package/dist/cjs/src/auth/certificates/index.js +1 -0
- package/dist/cjs/src/auth/certificates/index.js.map +1 -1
- package/dist/cjs/src/auth/utils/index.js +0 -1
- package/dist/cjs/src/auth/utils/index.js.map +1 -1
- package/dist/cjs/src/transaction/Beef.js +0 -1
- package/dist/cjs/src/transaction/Beef.js.map +1 -1
- package/dist/cjs/src/transaction/index.js +16 -3
- package/dist/cjs/src/transaction/index.js.map +1 -1
- package/dist/cjs/src/wallet/CachedKeyDeriver.js.map +1 -1
- package/dist/cjs/src/wallet/KeyDeriver.js +3 -2
- package/dist/cjs/src/wallet/KeyDeriver.js.map +1 -1
- package/dist/cjs/src/wallet/ProtoWallet.js +1 -1
- package/dist/cjs/src/wallet/ProtoWallet.js.map +1 -1
- package/dist/cjs/src/wallet/WalletClient.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/auth/Peer.js +1 -1
- package/dist/esm/src/auth/Peer.js.map +1 -1
- package/dist/esm/src/auth/certificates/Certificate.js +25 -6
- package/dist/esm/src/auth/certificates/Certificate.js.map +1 -1
- package/dist/esm/src/auth/certificates/MasterCertificate.js +100 -18
- package/dist/esm/src/auth/certificates/MasterCertificate.js.map +1 -1
- package/dist/esm/src/auth/certificates/VerifiableCertificate.js +3 -4
- package/dist/esm/src/auth/certificates/VerifiableCertificate.js.map +1 -1
- package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js +76 -0
- package/dist/esm/src/auth/certificates/__tests/CompletedProtoWallet.js.map +1 -0
- package/dist/esm/src/auth/certificates/index.js +1 -0
- package/dist/esm/src/auth/certificates/index.js.map +1 -1
- package/dist/esm/src/auth/utils/index.js +0 -1
- package/dist/esm/src/auth/utils/index.js.map +1 -1
- package/dist/esm/src/transaction/Beef.js +0 -1
- package/dist/esm/src/transaction/Beef.js.map +1 -1
- package/dist/esm/src/transaction/index.js +1 -1
- package/dist/esm/src/transaction/index.js.map +1 -1
- package/dist/esm/src/wallet/CachedKeyDeriver.js.map +1 -1
- package/dist/esm/src/wallet/KeyDeriver.js +3 -2
- package/dist/esm/src/wallet/KeyDeriver.js.map +1 -1
- package/dist/esm/src/wallet/ProtoWallet.js +1 -1
- package/dist/esm/src/wallet/ProtoWallet.js.map +1 -1
- package/dist/esm/src/wallet/WalletClient.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/auth/Peer.d.ts +1 -1
- package/dist/types/src/auth/Peer.d.ts.map +1 -1
- package/dist/types/src/auth/certificates/Certificate.d.ts +16 -3
- package/dist/types/src/auth/certificates/Certificate.d.ts.map +1 -1
- package/dist/types/src/auth/certificates/MasterCertificate.d.ts +46 -13
- package/dist/types/src/auth/certificates/MasterCertificate.d.ts.map +1 -1
- package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts +1 -1
- package/dist/types/src/auth/certificates/VerifiableCertificate.d.ts.map +1 -1
- package/dist/types/src/auth/certificates/__tests/CompletedProtoWallet.d.ts +24 -0
- package/dist/types/src/auth/certificates/__tests/CompletedProtoWallet.d.ts.map +1 -0
- package/dist/types/src/auth/certificates/index.d.ts +1 -0
- package/dist/types/src/auth/certificates/index.d.ts.map +1 -1
- package/dist/types/src/auth/utils/index.d.ts +0 -1
- package/dist/types/src/auth/utils/index.d.ts.map +1 -1
- package/dist/types/src/transaction/Beef.d.ts +0 -1
- package/dist/types/src/transaction/Beef.d.ts.map +1 -1
- package/dist/types/src/transaction/index.d.ts +1 -1
- package/dist/types/src/transaction/index.d.ts.map +1 -1
- package/dist/types/src/wallet/CachedKeyDeriver.d.ts.map +1 -1
- package/dist/types/src/wallet/KeyDeriver.d.ts +5 -7
- package/dist/types/src/wallet/KeyDeriver.d.ts.map +1 -1
- package/dist/types/src/wallet/ProtoWallet.d.ts.map +1 -1
- package/dist/types/src/wallet/Wallet.interfaces.d.ts.map +1 -1
- package/dist/types/src/wallet/WalletClient.d.ts +1 -2
- package/dist/types/src/wallet/WalletClient.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/auth.md +111 -87
- package/docs/wallet.md +26 -97
- package/package.json +1 -1
- package/src/auth/Peer.ts +23 -23
- package/src/auth/__tests/Peer.test.ts +19 -27
- package/src/auth/certificates/Certificate.ts +31 -8
- package/src/auth/certificates/MasterCertificate.ts +134 -23
- package/src/auth/certificates/VerifiableCertificate.ts +6 -6
- package/src/auth/certificates/__tests/Certificate.test.ts +45 -7
- package/src/auth/certificates/__tests/CompletedProtoWallet.ts +101 -0
- package/src/auth/certificates/__tests/MasterCertificate.test.ts +273 -0
- package/src/auth/certificates/__tests/VerifiableCertificate.test.ts +117 -0
- package/src/auth/certificates/index.ts +2 -1
- package/src/auth/utils/index.ts +0 -1
- package/src/transaction/Beef.ts +0 -2
- package/src/transaction/__tests/Beef.test.ts +1 -1
- package/src/transaction/index.ts +1 -1
- package/src/wallet/CachedKeyDeriver.ts +1 -2
- package/src/wallet/KeyDeriver.ts +18 -20
- package/src/wallet/ProtoWallet.ts +21 -21
- package/src/wallet/Wallet.interfaces.ts +8 -9
- package/src/wallet/WalletClient.ts +1 -2
- package/src/wallet/__tests/KeyDeriver.test.ts +2 -2
- package/dist/cjs/src/auth/utils/certificateHelpers.js +0 -51
- package/dist/cjs/src/auth/utils/certificateHelpers.js.map +0 -1
- package/dist/esm/src/auth/utils/certificateHelpers.js +0 -47
- package/dist/esm/src/auth/utils/certificateHelpers.js.map +0 -1
- package/dist/types/src/auth/utils/certificateHelpers.d.ts +0 -26
- package/dist/types/src/auth/utils/certificateHelpers.d.ts.map +0 -1
- package/src/auth/utils/certificateHelpers.ts +0 -86
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Certificate } from '../../../../dist/cjs/src/auth/index.js'
|
|
2
|
-
import {
|
|
2
|
+
import { CompletedProtoWallet } from '../../../../dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js'
|
|
3
3
|
import { Utils, PrivateKey } from '../../../../dist/cjs/src/primitives/index.js'
|
|
4
4
|
|
|
5
5
|
describe('Certificate', () => {
|
|
@@ -73,7 +73,7 @@ describe('Certificate', () => {
|
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
// Sign the certificate
|
|
76
|
-
const certifierWallet = new
|
|
76
|
+
const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
|
|
77
77
|
await certificate.sign(certifierWallet)
|
|
78
78
|
|
|
79
79
|
const serialized = certificate.toBinary(true) // Include signature
|
|
@@ -100,7 +100,7 @@ describe('Certificate', () => {
|
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
// Sign the certificate
|
|
103
|
-
const certifierWallet = new
|
|
103
|
+
const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
|
|
104
104
|
await certificate.sign(certifierWallet)
|
|
105
105
|
|
|
106
106
|
// Verify the signature
|
|
@@ -120,7 +120,7 @@ describe('Certificate', () => {
|
|
|
120
120
|
)
|
|
121
121
|
|
|
122
122
|
// Sign the certificate
|
|
123
|
-
const certifierWallet = new
|
|
123
|
+
const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
|
|
124
124
|
await certificate.sign(certifierWallet)
|
|
125
125
|
|
|
126
126
|
// Tamper with the certificate (modify a field)
|
|
@@ -172,7 +172,7 @@ describe('Certificate', () => {
|
|
|
172
172
|
)
|
|
173
173
|
|
|
174
174
|
// Sign the certificate
|
|
175
|
-
const certifierWallet = new
|
|
175
|
+
const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
|
|
176
176
|
await certificate.sign(certifierWallet)
|
|
177
177
|
|
|
178
178
|
// Serialize and deserialize
|
|
@@ -223,7 +223,7 @@ describe('Certificate', () => {
|
|
|
223
223
|
)
|
|
224
224
|
|
|
225
225
|
// Sign the certificate
|
|
226
|
-
const certifierWallet = new
|
|
226
|
+
const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
|
|
227
227
|
await certificate.sign(certifierWallet)
|
|
228
228
|
|
|
229
229
|
// Serialize and deserialize
|
|
@@ -266,7 +266,7 @@ describe('Certificate', () => {
|
|
|
266
266
|
)
|
|
267
267
|
|
|
268
268
|
// Sign the certificate
|
|
269
|
-
const certifierWallet = new
|
|
269
|
+
const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
|
|
270
270
|
await certificate.sign(certifierWallet)
|
|
271
271
|
|
|
272
272
|
// Serialize and deserialize
|
|
@@ -279,4 +279,42 @@ describe('Certificate', () => {
|
|
|
279
279
|
const isValid = await deserializedCertificate.verify()
|
|
280
280
|
expect(isValid).toBe(true)
|
|
281
281
|
})
|
|
282
|
+
|
|
283
|
+
it('should throw if already signed, and should update the certifier field if it differs from the wallet\'s public key', async () => {
|
|
284
|
+
// Scenario 1: Certificate already has a signature
|
|
285
|
+
const preSignedCertificate = new Certificate(
|
|
286
|
+
sampleType,
|
|
287
|
+
sampleSerialNumber,
|
|
288
|
+
sampleSubjectPubKey,
|
|
289
|
+
sampleCertifierPubKey, // We'll pretend this was signed by them
|
|
290
|
+
sampleRevocationOutpoint,
|
|
291
|
+
sampleFields,
|
|
292
|
+
'deadbeef' // Already has a placeholder signature
|
|
293
|
+
)
|
|
294
|
+
const certifierWallet = new CompletedProtoWallet(sampleCertifierPrivateKey)
|
|
295
|
+
|
|
296
|
+
// Trying to sign again should throw
|
|
297
|
+
await expect(preSignedCertificate.sign(certifierWallet)).rejects.toThrow(
|
|
298
|
+
'Certificate has already been signed!'
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
// Scenario 2: The certifier property is set to something different from the wallet's public key
|
|
302
|
+
const mismatchedCertifierPubKey = PrivateKey.fromRandom().toPublicKey().toString()
|
|
303
|
+
const certificateWithMismatch = new Certificate(
|
|
304
|
+
sampleType,
|
|
305
|
+
sampleSerialNumber,
|
|
306
|
+
sampleSubjectPubKey,
|
|
307
|
+
mismatchedCertifierPubKey, // Different from actual wallet key
|
|
308
|
+
sampleRevocationOutpoint,
|
|
309
|
+
sampleFields
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
// Sign the certificate; it should automatically update
|
|
313
|
+
// the certifier field to match the wallet's actual public key
|
|
314
|
+
const certifierPubKey = ((await certifierWallet.getPublicKey({ identityKey: true })).publicKey)
|
|
315
|
+
await certificateWithMismatch.sign(certifierWallet)
|
|
316
|
+
expect(certificateWithMismatch.certifier).toBe(certifierPubKey)
|
|
317
|
+
expect(await certificateWithMismatch.verify()).toBe(true)
|
|
318
|
+
})
|
|
319
|
+
|
|
282
320
|
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { PrivateKey } from "mod.js"
|
|
2
|
+
import { ProtoWallet, Wallet, CreateActionArgs, OriginatorDomainNameStringUnder250Bytes, CreateActionResult, SignActionArgs, SignActionResult, AbortActionArgs, AbortActionResult, ListActionsArgs, ListActionsResult, InternalizeActionArgs, InternalizeActionResult, ListOutputsArgs, ListOutputsResult, RelinquishOutputArgs, RelinquishOutputResult, AcquireCertificateArgs, AcquireCertificateResult, ListCertificatesArgs, ListCertificatesResult, ProveCertificateArgs, ProveCertificateResult, RelinquishCertificateArgs, RelinquishCertificateResult, DiscoverByIdentityKeyArgs, DiscoverCertificatesResult, DiscoverByAttributesArgs, GetHeightResult, GetHeaderArgs, GetHeaderResult, KeyDeriverApi, KeyDeriver, GetPublicKeyArgs, GetPublicKeyResult, PubKeyHex } from "../../../wallet/index.js"
|
|
3
|
+
|
|
4
|
+
// Test Mock wallet which extends ProtoWallet but still implements Wallet interface
|
|
5
|
+
// Unsupported methods throw
|
|
6
|
+
export class CompletedProtoWallet extends ProtoWallet implements Wallet {
|
|
7
|
+
constructor(rootKeyOrKeyDeriver: PrivateKey | 'anyone' | KeyDeriverApi) {
|
|
8
|
+
super(rootKeyOrKeyDeriver)
|
|
9
|
+
if (typeof rootKeyOrKeyDeriver['identityKey'] !== 'string') {
|
|
10
|
+
rootKeyOrKeyDeriver = new KeyDeriver(rootKeyOrKeyDeriver as PrivateKey | 'anyone')
|
|
11
|
+
}
|
|
12
|
+
this.keyDeriver = rootKeyOrKeyDeriver as KeyDeriver
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async getPublicKey(
|
|
16
|
+
args: GetPublicKeyArgs,
|
|
17
|
+
originator?: OriginatorDomainNameStringUnder250Bytes
|
|
18
|
+
): Promise<{ publicKey: PubKeyHex }> {
|
|
19
|
+
if (args.privileged) {
|
|
20
|
+
throw new Error('no privilege support')
|
|
21
|
+
}
|
|
22
|
+
if (args.identityKey) {
|
|
23
|
+
return { publicKey: this.keyDeriver.rootKey.toPublicKey().toString() }
|
|
24
|
+
} else {
|
|
25
|
+
if (!args.protocolID || !args.keyID) {
|
|
26
|
+
throw new Error('protocolID and keyID are required if identityKey is false or undefined.')
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
publicKey: this.keyDeriver
|
|
30
|
+
.derivePublicKey(
|
|
31
|
+
args.protocolID,
|
|
32
|
+
args.keyID,
|
|
33
|
+
args.counterparty || 'self',
|
|
34
|
+
args.forSelf
|
|
35
|
+
)
|
|
36
|
+
.toString()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async createAction(args: CreateActionArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
42
|
+
: Promise<CreateActionResult> {
|
|
43
|
+
throw new Error("not implemented")
|
|
44
|
+
}
|
|
45
|
+
async signAction(args: SignActionArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
46
|
+
: Promise<SignActionResult> {
|
|
47
|
+
throw new Error("not implemented")
|
|
48
|
+
}
|
|
49
|
+
async abortAction(args: AbortActionArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
50
|
+
: Promise<AbortActionResult> {
|
|
51
|
+
throw new Error("not implemented")
|
|
52
|
+
}
|
|
53
|
+
async listActions(args: ListActionsArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
54
|
+
: Promise<ListActionsResult> {
|
|
55
|
+
throw new Error("not implemented")
|
|
56
|
+
}
|
|
57
|
+
async internalizeAction(args: InternalizeActionArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
58
|
+
: Promise<InternalizeActionResult> {
|
|
59
|
+
throw new Error("not implemented")
|
|
60
|
+
}
|
|
61
|
+
async listOutputs(args: ListOutputsArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
62
|
+
: Promise<ListOutputsResult> {
|
|
63
|
+
throw new Error("not implemented")
|
|
64
|
+
}
|
|
65
|
+
async relinquishOutput(args: RelinquishOutputArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
66
|
+
: Promise<RelinquishOutputResult> {
|
|
67
|
+
throw new Error("not implemented")
|
|
68
|
+
}
|
|
69
|
+
async acquireCertificate(args: AcquireCertificateArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
70
|
+
: Promise<AcquireCertificateResult> {
|
|
71
|
+
throw new Error("not implemented")
|
|
72
|
+
}
|
|
73
|
+
async listCertificates(args: ListCertificatesArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
74
|
+
: Promise<ListCertificatesResult> {
|
|
75
|
+
throw new Error("not implemented")
|
|
76
|
+
}
|
|
77
|
+
async proveCertificate(args: ProveCertificateArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
78
|
+
: Promise<ProveCertificateResult> {
|
|
79
|
+
throw new Error("not implemented")
|
|
80
|
+
}
|
|
81
|
+
async relinquishCertificate(args: RelinquishCertificateArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
82
|
+
: Promise<RelinquishCertificateResult> {
|
|
83
|
+
throw new Error("not implemented")
|
|
84
|
+
}
|
|
85
|
+
async discoverByIdentityKey(args: DiscoverByIdentityKeyArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
86
|
+
: Promise<DiscoverCertificatesResult> {
|
|
87
|
+
throw new Error("not implemented")
|
|
88
|
+
}
|
|
89
|
+
async discoverByAttributes(args: DiscoverByAttributesArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
90
|
+
: Promise<DiscoverCertificatesResult> {
|
|
91
|
+
throw new Error("not implemented")
|
|
92
|
+
}
|
|
93
|
+
async getHeight(args: {}, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
94
|
+
: Promise<GetHeightResult> {
|
|
95
|
+
throw new Error("not implemented")
|
|
96
|
+
}
|
|
97
|
+
async getHeaderForHeight(args: GetHeaderArgs, originator?: OriginatorDomainNameStringUnder250Bytes)
|
|
98
|
+
: Promise<GetHeaderResult> {
|
|
99
|
+
throw new Error("not implemented")
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { MasterCertificate } from '../../../../dist/cjs/src/auth/certificates/MasterCertificate.js'
|
|
2
|
+
import { VerifiableCertificate } from '../../../../dist/cjs/src/auth/certificates/VerifiableCertificate.js'
|
|
3
|
+
import { PrivateKey, SymmetricKey, Utils, Random } from '../../../../dist/cjs/src/primitives/index.js'
|
|
4
|
+
import { CompletedProtoWallet } from '../../../../dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js'
|
|
5
|
+
|
|
6
|
+
describe('MasterCertificate', () => {
|
|
7
|
+
const subjectPrivateKey = PrivateKey.fromRandom()
|
|
8
|
+
const certifierPrivateKey = PrivateKey.fromRandom()
|
|
9
|
+
|
|
10
|
+
// A mock revocation outpoint for testing
|
|
11
|
+
const mockRevocationOutpoint = 'deadbeefdeadbeefdeadbeefdeadbeef00000000000000000000000000000000.1'
|
|
12
|
+
|
|
13
|
+
// Arbitrary certificate data (in plaintext)
|
|
14
|
+
const plaintextFields = {
|
|
15
|
+
name: 'Alice',
|
|
16
|
+
email: 'alice@example.com',
|
|
17
|
+
department: 'Engineering'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const subjectWallet = new CompletedProtoWallet(subjectPrivateKey)
|
|
21
|
+
const certifierWallet = new CompletedProtoWallet(certifierPrivateKey)
|
|
22
|
+
let subjectPubKey, certifierPubKey
|
|
23
|
+
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
subjectPubKey = (await subjectWallet.getPublicKey({ identityKey: true })).publicKey
|
|
26
|
+
certifierPubKey = (await certifierWallet.getPublicKey({ identityKey: true })).publicKey
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('constructor', () => {
|
|
30
|
+
it('should construct a MasterCertificate successfully when masterKeyring is valid', () => {
|
|
31
|
+
// Prepare a minimal valid MasterCertificate
|
|
32
|
+
const fieldSymKey = SymmetricKey.fromRandom()
|
|
33
|
+
const encryptedFieldValue = Utils.toBase64(
|
|
34
|
+
fieldSymKey.encrypt(Utils.toArray('Alice', 'utf8')) as number[]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
const encryptedKeyForSubject = Utils.toBase64([0, 1, 2, 3])
|
|
38
|
+
// We assume we have the same fieldName in both `fields` and `masterKeyring`.
|
|
39
|
+
const fields = { name: encryptedFieldValue }
|
|
40
|
+
const masterKeyring = { name: encryptedKeyForSubject }
|
|
41
|
+
|
|
42
|
+
const certificate = new MasterCertificate(
|
|
43
|
+
Utils.toBase64(Random(16)), // type
|
|
44
|
+
Utils.toBase64(Random(16)), // serialNumber
|
|
45
|
+
subjectPubKey,
|
|
46
|
+
certifierPubKey,
|
|
47
|
+
mockRevocationOutpoint,
|
|
48
|
+
fields,
|
|
49
|
+
masterKeyring
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
expect(certificate).toBeInstanceOf(MasterCertificate)
|
|
53
|
+
expect(certificate.fields).toEqual(fields)
|
|
54
|
+
expect(certificate.masterKeyring).toEqual(masterKeyring)
|
|
55
|
+
expect(certificate.subject).toEqual(subjectPubKey)
|
|
56
|
+
expect(certificate.certifier).toEqual(certifierPubKey)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should throw if masterKeyring is missing a key for any field', () => {
|
|
60
|
+
const fields = { name: 'encrypted_value' }
|
|
61
|
+
const masterKeyring = {} // intentionally empty
|
|
62
|
+
|
|
63
|
+
expect(() => {
|
|
64
|
+
new MasterCertificate(
|
|
65
|
+
Utils.toBase64(Random(16)), // type
|
|
66
|
+
Utils.toBase64(Random(16)), // serialNumber
|
|
67
|
+
subjectPubKey,
|
|
68
|
+
certifierPubKey,
|
|
69
|
+
mockRevocationOutpoint,
|
|
70
|
+
fields,
|
|
71
|
+
masterKeyring
|
|
72
|
+
)
|
|
73
|
+
}).toThrowError(/Master keyring must contain a value for every field/)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('decryptFields', () => {
|
|
78
|
+
it('should decrypt all fields correctly using subject wallet', async () => {
|
|
79
|
+
// Issue a certificate for the subject, which includes a valid masterKeyring
|
|
80
|
+
const certificate = await MasterCertificate.issueCertificateForSubject(
|
|
81
|
+
certifierWallet,
|
|
82
|
+
subjectPubKey,
|
|
83
|
+
plaintextFields,
|
|
84
|
+
'TEST_CERT'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
// Now subject should be able to decrypt all fields
|
|
88
|
+
const decrypted = await certificate.decryptFields(subjectWallet)
|
|
89
|
+
expect(decrypted).toEqual(plaintextFields)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should throw if masterKeyring is empty or invalid', async () => {
|
|
93
|
+
// Manually create a MasterCertificate with an empty masterKeyring
|
|
94
|
+
expect(() => new MasterCertificate(
|
|
95
|
+
Utils.toBase64(Random(16)),
|
|
96
|
+
Utils.toBase64(Random(16)),
|
|
97
|
+
subjectPubKey,
|
|
98
|
+
certifierPubKey,
|
|
99
|
+
mockRevocationOutpoint,
|
|
100
|
+
{ name: Utils.toBase64([1, 2, 3]) },
|
|
101
|
+
{}
|
|
102
|
+
)).toThrow("Master keyring must contain a value for every field. Missing key for field: \"name\"");
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should throw if decryption fails for any field', async () => {
|
|
106
|
+
// Manually craft a scenario where the key is incorrect
|
|
107
|
+
const badKeyMasterKeyring = Utils.toBase64([9, 9, 9, 9]) // Not the correct key
|
|
108
|
+
const badKeyCertificate = new MasterCertificate(
|
|
109
|
+
Utils.toBase64(Random(16)),
|
|
110
|
+
Utils.toBase64(Random(16)),
|
|
111
|
+
subjectPubKey,
|
|
112
|
+
certifierPubKey,
|
|
113
|
+
mockRevocationOutpoint,
|
|
114
|
+
{
|
|
115
|
+
name: Utils.toBase64(SymmetricKey.fromRandom().encrypt(Utils.toArray('Alice', 'utf8')) as number[])
|
|
116
|
+
},
|
|
117
|
+
{ name: badKeyMasterKeyring }
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
await expect(badKeyCertificate.decryptFields(subjectWallet))
|
|
121
|
+
.rejects
|
|
122
|
+
.toThrow('Failed to decrypt all master certificate fields.')
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('createKeyringForVerifier', () => {
|
|
127
|
+
const verifierPrivateKey = PrivateKey.fromRandom()
|
|
128
|
+
const verifierWallet = new CompletedProtoWallet(verifierPrivateKey)
|
|
129
|
+
let verifierPubKey
|
|
130
|
+
|
|
131
|
+
let issuedCert: MasterCertificate
|
|
132
|
+
|
|
133
|
+
beforeAll(async () => {
|
|
134
|
+
verifierPubKey = (await verifierWallet.getPublicKey({ identityKey: true })).publicKey
|
|
135
|
+
issuedCert = await MasterCertificate.issueCertificateForSubject(
|
|
136
|
+
certifierWallet,
|
|
137
|
+
subjectPubKey,
|
|
138
|
+
plaintextFields,
|
|
139
|
+
'TEST_CERT'
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('should create a verifier keyring for specified fields', async () => {
|
|
144
|
+
// We only want to share "name" with the verifier
|
|
145
|
+
const fieldsToReveal = ['name']
|
|
146
|
+
const keyringForVerifier = await issuedCert.createKeyringForVerifier(
|
|
147
|
+
subjectWallet,
|
|
148
|
+
verifierPubKey,
|
|
149
|
+
fieldsToReveal
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
// The new keyring should only contain "name"
|
|
153
|
+
expect(Object.keys(keyringForVerifier)).toHaveLength(1)
|
|
154
|
+
expect(keyringForVerifier).toHaveProperty('name')
|
|
155
|
+
|
|
156
|
+
// Now let's create a VerifiableCertificate for the verifier
|
|
157
|
+
const verifiableCert = new VerifiableCertificate(
|
|
158
|
+
issuedCert.type,
|
|
159
|
+
issuedCert.serialNumber,
|
|
160
|
+
issuedCert.subject,
|
|
161
|
+
issuedCert.certifier,
|
|
162
|
+
issuedCert.revocationOutpoint,
|
|
163
|
+
issuedCert.fields,
|
|
164
|
+
issuedCert.signature,
|
|
165
|
+
keyringForVerifier
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
// The verifier should successfully decrypt the "name" field
|
|
169
|
+
const decrypted = await verifiableCert.decryptFields(verifierWallet)
|
|
170
|
+
expect(decrypted).toEqual({ name: plaintextFields.name })
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should throw if fields to reveal are not subset of the certificate fields', async () => {
|
|
174
|
+
await expect(
|
|
175
|
+
issuedCert.createKeyringForVerifier(subjectWallet, verifierPubKey, ['nonexistent_field'])
|
|
176
|
+
).rejects.toThrow(
|
|
177
|
+
/Fields to reveal must be a subset of the certificate fields\. Missing the "nonexistent_field" field\./
|
|
178
|
+
)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should throw if the master key fails to decrypt the corresponding field', async () => {
|
|
182
|
+
// We'll tamper the certificate's masterKeyring so that a field key is invalid
|
|
183
|
+
const tamperedCert = new MasterCertificate(
|
|
184
|
+
issuedCert.type,
|
|
185
|
+
issuedCert.serialNumber,
|
|
186
|
+
issuedCert.subject,
|
|
187
|
+
issuedCert.certifier,
|
|
188
|
+
issuedCert.revocationOutpoint,
|
|
189
|
+
issuedCert.fields,
|
|
190
|
+
{
|
|
191
|
+
// Tamper: replace 'name' field with nonsense
|
|
192
|
+
name: Utils.toBase64([66, 66, 66]),
|
|
193
|
+
email: issuedCert.masterKeyring.email,
|
|
194
|
+
department: issuedCert.masterKeyring.department
|
|
195
|
+
},
|
|
196
|
+
issuedCert.signature
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
await expect(
|
|
200
|
+
tamperedCert.createKeyringForVerifier(subjectWallet, verifierPubKey, ['name'])
|
|
201
|
+
).rejects.toThrow('Decryption failed!')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should support optional originator parameter', async () => {
|
|
205
|
+
// Just to ensure coverage for the originator-based flows
|
|
206
|
+
const fieldsToReveal = ['name']
|
|
207
|
+
const keyringForVerifier = await issuedCert.createKeyringForVerifier(
|
|
208
|
+
subjectWallet,
|
|
209
|
+
verifierPubKey,
|
|
210
|
+
fieldsToReveal,
|
|
211
|
+
'my-originator'
|
|
212
|
+
)
|
|
213
|
+
expect(keyringForVerifier).toHaveProperty('name')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('should support counterparty of "anyone"', async () => {
|
|
217
|
+
// Create a keyring for public disclosure of selected fields.
|
|
218
|
+
const fieldsToReveal = ['name']
|
|
219
|
+
const keyringForVerifier = await issuedCert.createKeyringForVerifier(
|
|
220
|
+
subjectWallet,
|
|
221
|
+
'anyone',
|
|
222
|
+
fieldsToReveal,
|
|
223
|
+
'my-originator'
|
|
224
|
+
)
|
|
225
|
+
expect(keyringForVerifier).toHaveProperty('name')
|
|
226
|
+
})
|
|
227
|
+
it('should support counterparty of "self"', async () => {
|
|
228
|
+
const fieldsToReveal = ['name']
|
|
229
|
+
const keyringForVerifier = await issuedCert.createKeyringForVerifier(
|
|
230
|
+
subjectWallet,
|
|
231
|
+
'self',
|
|
232
|
+
fieldsToReveal,
|
|
233
|
+
'my-originator'
|
|
234
|
+
)
|
|
235
|
+
expect(keyringForVerifier).toHaveProperty('name')
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
describe('issueCertificateForSubject', () => {
|
|
240
|
+
it('should issue a valid MasterCertificate for the given subject', async () => {
|
|
241
|
+
const newPlaintextFields = {
|
|
242
|
+
project: 'Top Secret',
|
|
243
|
+
clearanceLevel: 'High'
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const revocationFn = jest.fn().mockResolvedValue(mockRevocationOutpoint)
|
|
247
|
+
|
|
248
|
+
const newCert = await MasterCertificate.issueCertificateForSubject(
|
|
249
|
+
certifierWallet,
|
|
250
|
+
subjectPubKey,
|
|
251
|
+
newPlaintextFields,
|
|
252
|
+
'TEST_CERT',
|
|
253
|
+
revocationFn,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
expect(newCert).toBeInstanceOf(MasterCertificate)
|
|
257
|
+
// The certificate's fields should be encrypted base64
|
|
258
|
+
for (const fieldName in newPlaintextFields) {
|
|
259
|
+
expect(newCert.fields[fieldName]).toMatch(/^[A-Za-z0-9+/]+=*$/) // base64 check
|
|
260
|
+
}
|
|
261
|
+
// The masterKeyring should also contain base64 strings
|
|
262
|
+
for (const fieldName in newPlaintextFields) {
|
|
263
|
+
expect(newCert.masterKeyring[fieldName]).toMatch(/^[A-Za-z0-9+/]+=*$/)
|
|
264
|
+
}
|
|
265
|
+
// Check revocation outpoint is from mock
|
|
266
|
+
expect(newCert.revocationOutpoint).toEqual(mockRevocationOutpoint)
|
|
267
|
+
// Check we have a signature
|
|
268
|
+
expect(newCert.signature).toBeDefined()
|
|
269
|
+
// Check that the revocationFn were called
|
|
270
|
+
expect(revocationFn).toHaveBeenCalledWith(newCert.serialNumber)
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { VerifiableCertificate } from '../../../../dist/cjs/src/auth/certificates/VerifiableCertificate.js'
|
|
2
|
+
import { PrivateKey, SymmetricKey, Utils } from '../../../../dist/cjs/src/primitives/index.js'
|
|
3
|
+
import { CompletedProtoWallet } from '../../../../dist/cjs/src/auth/certificates/__tests/CompletedProtoWallet.js'
|
|
4
|
+
import { Certificate } from '../../../../dist/cjs/src/auth/certificates/index.js'
|
|
5
|
+
|
|
6
|
+
describe('VerifiableCertificate', () => {
|
|
7
|
+
const subjectPrivateKey = PrivateKey.fromRandom()
|
|
8
|
+
const subjectPubKey = subjectPrivateKey.toPublicKey().toString()
|
|
9
|
+
const certifierPrivateKey = PrivateKey.fromRandom()
|
|
10
|
+
const certifierPubKey = certifierPrivateKey.toPublicKey().toString()
|
|
11
|
+
const verifierPrivateKey = PrivateKey.fromRandom()
|
|
12
|
+
const verifierPubKey = verifierPrivateKey.toPublicKey().toString()
|
|
13
|
+
|
|
14
|
+
const subjectWallet = new CompletedProtoWallet(subjectPrivateKey)
|
|
15
|
+
const verifierWallet = new CompletedProtoWallet(verifierPrivateKey)
|
|
16
|
+
|
|
17
|
+
const sampleType = Utils.toBase64(new Array(32).fill(1))
|
|
18
|
+
const sampleSerialNumber = Utils.toBase64(new Array(32).fill(2))
|
|
19
|
+
const sampleRevocationOutpoint = 'deadbeefdeadbeefdeadbeefdeadbeef00000000000000000000000000000000.1'
|
|
20
|
+
|
|
21
|
+
const plaintextFields = {
|
|
22
|
+
name: 'Alice',
|
|
23
|
+
email: 'alice@example.com',
|
|
24
|
+
organization: 'Example Corp'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let verifiableCert: VerifiableCertificate
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
// For each test, we'll build a fresh VerifiableCertificate with valid encryption
|
|
31
|
+
const certificateFields = {}
|
|
32
|
+
const keyring = {}
|
|
33
|
+
|
|
34
|
+
for (const fieldName in plaintextFields) {
|
|
35
|
+
// Generate a random field symmetric key
|
|
36
|
+
const fieldSymKey = SymmetricKey.fromRandom()
|
|
37
|
+
// Encrypt the field's plaintext
|
|
38
|
+
const encryptedFieldValue = fieldSymKey.encrypt(Utils.toArray(plaintextFields[fieldName], 'utf8'))
|
|
39
|
+
certificateFields[fieldName] = Utils.toBase64(encryptedFieldValue as number[])
|
|
40
|
+
|
|
41
|
+
// Now encrypt the fieldSymKey for the verifier
|
|
42
|
+
const { ciphertext: encryptedRevelationKey } = await subjectWallet.encrypt({
|
|
43
|
+
plaintext: fieldSymKey.toArray(),
|
|
44
|
+
...Certificate.getCertificateFieldEncryptionDetails(sampleSerialNumber, fieldName),
|
|
45
|
+
counterparty: verifierPubKey
|
|
46
|
+
})
|
|
47
|
+
keyring[fieldName] = Utils.toBase64(encryptedRevelationKey)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
verifiableCert = new VerifiableCertificate(
|
|
51
|
+
sampleType,
|
|
52
|
+
sampleSerialNumber,
|
|
53
|
+
subjectPubKey,
|
|
54
|
+
certifierPubKey,
|
|
55
|
+
sampleRevocationOutpoint,
|
|
56
|
+
certificateFields,
|
|
57
|
+
undefined, // signature
|
|
58
|
+
keyring
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('constructor', () => {
|
|
63
|
+
it('should create a VerifiableCertificate with all required properties', () => {
|
|
64
|
+
expect(verifiableCert).toBeInstanceOf(VerifiableCertificate)
|
|
65
|
+
expect(verifiableCert.type).toEqual(sampleType)
|
|
66
|
+
expect(verifiableCert.serialNumber).toEqual(sampleSerialNumber)
|
|
67
|
+
expect(verifiableCert.subject).toEqual(subjectPubKey)
|
|
68
|
+
expect(verifiableCert.certifier).toEqual(certifierPubKey)
|
|
69
|
+
expect(verifiableCert.revocationOutpoint).toEqual(sampleRevocationOutpoint)
|
|
70
|
+
expect(verifiableCert.fields).toBeDefined()
|
|
71
|
+
expect(verifiableCert.keyring).toBeDefined()
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('decryptFields', () => {
|
|
76
|
+
it('should decrypt fields successfully when provided the correct verifier wallet and keyring', async () => {
|
|
77
|
+
const decrypted = await verifiableCert.decryptFields(verifierWallet)
|
|
78
|
+
expect(decrypted).toEqual(plaintextFields)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should fail if the verifier wallet does not have the correct private key (wrong key)', async () => {
|
|
82
|
+
const wrongPrivateKey = PrivateKey.fromRandom()
|
|
83
|
+
const wrongWallet = new CompletedProtoWallet(wrongPrivateKey)
|
|
84
|
+
|
|
85
|
+
await expect(verifiableCert.decryptFields(wrongWallet)).rejects.toThrow(
|
|
86
|
+
/Failed to decrypt selectively revealed certificate fields using keyring/
|
|
87
|
+
)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should fail if the keyring is empty or missing keys', async () => {
|
|
91
|
+
// Create a new VerifiableCertificate but with an empty keyring
|
|
92
|
+
const fields = verifiableCert.fields
|
|
93
|
+
const emptyKeyringCert = new VerifiableCertificate(
|
|
94
|
+
verifiableCert.type,
|
|
95
|
+
verifiableCert.serialNumber,
|
|
96
|
+
verifiableCert.subject,
|
|
97
|
+
verifiableCert.certifier,
|
|
98
|
+
verifiableCert.revocationOutpoint,
|
|
99
|
+
fields,
|
|
100
|
+
verifiableCert.signature,
|
|
101
|
+
{} // empty
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
await expect(emptyKeyringCert.decryptFields(verifierWallet)).rejects.toThrow(
|
|
105
|
+
'A keyring is required to decrypt certificate fields for the verifier.'
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should fail if the encrypted field or its key is tampered', async () => {
|
|
110
|
+
// Tamper the keyring so it doesn't match the field encryption
|
|
111
|
+
verifiableCert.keyring.name = Utils.toBase64([9, 9, 9, 9])
|
|
112
|
+
await expect(verifiableCert.decryptFields(verifierWallet)).rejects.toThrow(
|
|
113
|
+
/Failed to decrypt selectively revealed certificate fields using keyring/
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
})
|
package/src/auth/utils/index.ts
CHANGED
package/src/transaction/Beef.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The following imports allow flag a large number type checking errors by the VsCode editor due to missing type information:
|
|
2
2
|
import BeefTx from "../../../dist/cjs/src/transaction/BeefTx"
|
|
3
3
|
import BeefParty from "../../../dist/cjs/src/transaction/BeefParty"
|
|
4
|
-
import Beef,
|
|
4
|
+
import { Beef, BEEF_V1, BEEF_V2 } from "../../../dist/cjs/src/transaction/Beef"
|
|
5
5
|
import Transaction from "../../../dist/cjs/src/transaction/Transaction"
|
|
6
6
|
import { fromBase58 } from '../../../dist/cjs/src/primitives/utils'
|
|
7
7
|
|
package/src/transaction/index.ts
CHANGED
|
@@ -6,5 +6,5 @@ export type { Broadcaster, BroadcastFailure, BroadcastResponse } from './Broadca
|
|
|
6
6
|
export { isBroadcastResponse, isBroadcastFailure } from './Broadcaster.js'
|
|
7
7
|
export type { default as ChainTracker } from './ChainTracker.js'
|
|
8
8
|
export { default as BeefTx } from './BeefTx.js'
|
|
9
|
-
export
|
|
9
|
+
export * from './Beef.js'
|
|
10
10
|
export { default as BeefParty } from './BeefParty.js'
|
|
@@ -24,7 +24,6 @@ export default class CachedKeyDeriver {
|
|
|
24
24
|
this.maxCacheSize = options?.maxCacheSize || 1000
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
27
|
/**
|
|
29
28
|
* Derives a public key based on protocol ID, key ID, and counterparty.
|
|
30
29
|
* Caches the result for future calls with the same parameters.
|
|
@@ -188,7 +187,7 @@ export default class CachedKeyDeriver {
|
|
|
188
187
|
if (this.cache.size >= this.maxCacheSize) {
|
|
189
188
|
// Evict the least recently used item (first item in Map)
|
|
190
189
|
const firstKey = this.cache.keys().next().value
|
|
191
|
-
this.cache.delete(firstKey
|
|
190
|
+
this.cache.delete(firstKey)
|
|
192
191
|
}
|
|
193
192
|
this.cache.set(cacheKey, value)
|
|
194
193
|
}
|