@peerbit/crypto 1.0.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/LICENSE +202 -0
- package/README.md +3 -0
- package/lib/esm/bytes.d.ts +5 -0
- package/lib/esm/bytes.js +15 -0
- package/lib/esm/bytes.js.map +1 -0
- package/lib/esm/ed25519-sign-browser.d.ts +5 -0
- package/lib/esm/ed25519-sign-browser.js +27 -0
- package/lib/esm/ed25519-sign-browser.js.map +1 -0
- package/lib/esm/ed25519-sign.d.ts +5 -0
- package/lib/esm/ed25519-sign.js +64 -0
- package/lib/esm/ed25519-sign.js.map +1 -0
- package/lib/esm/ed25519.d.ts +41 -0
- package/lib/esm/ed25519.js +181 -0
- package/lib/esm/ed25519.js.map +1 -0
- package/lib/esm/encryption.d.ts +76 -0
- package/lib/esm/encryption.js +350 -0
- package/lib/esm/encryption.js.map +1 -0
- package/lib/esm/errors.d.ts +3 -0
- package/lib/esm/errors.js +6 -0
- package/lib/esm/errors.js.map +1 -0
- package/lib/esm/hash-browser.d.ts +4 -0
- package/lib/esm/hash-browser.js +7 -0
- package/lib/esm/hash-browser.js.map +1 -0
- package/lib/esm/hash.d.ts +4 -0
- package/lib/esm/hash.js +6 -0
- package/lib/esm/hash.js.map +1 -0
- package/lib/esm/index.d.ts +14 -0
- package/lib/esm/index.js +15 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/esm/key.d.ts +44 -0
- package/lib/esm/key.js +51 -0
- package/lib/esm/key.js.map +1 -0
- package/lib/esm/keychain.d.ts +30 -0
- package/lib/esm/keychain.js +142 -0
- package/lib/esm/keychain.js.map +1 -0
- package/lib/esm/libp2p.d.ts +5 -0
- package/lib/esm/libp2p.js +21 -0
- package/lib/esm/libp2p.js.map +1 -0
- package/lib/esm/prehash.d.ts +6 -0
- package/lib/esm/prehash.js +32 -0
- package/lib/esm/prehash.js.map +1 -0
- package/lib/esm/random-browser.d.ts +1 -0
- package/lib/esm/random-browser.js +2 -0
- package/lib/esm/random-browser.js.map +1 -0
- package/lib/esm/random.d.ts +2 -0
- package/lib/esm/random.js +3 -0
- package/lib/esm/random.js.map +1 -0
- package/lib/esm/sepc256k1.d.ts +42 -0
- package/lib/esm/sepc256k1.js +194 -0
- package/lib/esm/sepc256k1.js.map +1 -0
- package/lib/esm/signature.d.ts +34 -0
- package/lib/esm/signature.js +283 -0
- package/lib/esm/signature.js.map +1 -0
- package/lib/esm/signer.d.ts +10 -0
- package/lib/esm/signer.js +2 -0
- package/lib/esm/signer.js.map +1 -0
- package/lib/esm/utils.d.ts +4 -0
- package/lib/esm/utils.js +10 -0
- package/lib/esm/utils.js.map +1 -0
- package/lib/esm/x25519.d.ts +38 -0
- package/lib/esm/x25519.js +158 -0
- package/lib/esm/x25519.js.map +1 -0
- package/package.json +55 -0
- package/src/bytes.ts +12 -0
- package/src/ed25519-sign-browser.ts +43 -0
- package/src/ed25519-sign.ts +83 -0
- package/src/ed25519.ts +194 -0
- package/src/encryption.ts +376 -0
- package/src/errors.ts +5 -0
- package/src/hash-browser.ts +10 -0
- package/src/hash.ts +10 -0
- package/src/index.ts +14 -0
- package/src/key.ts +80 -0
- package/src/keychain.ts +197 -0
- package/src/libp2p.ts +27 -0
- package/src/prehash.ts +42 -0
- package/src/random-browser.ts +2 -0
- package/src/random.ts +2 -0
- package/src/sepc256k1.ts +229 -0
- package/src/signature.ts +284 -0
- package/src/signer.ts +18 -0
- package/src/utils.ts +15 -0
- package/src/x25519.ts +167 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Ed25519Keypair, Ed25519PublicKey } from "./ed25519.js";
|
|
2
|
+
import sodium from "libsodium-wrappers";
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
import { SignatureWithKey } from "./signature.js";
|
|
5
|
+
import { PreHash, prehashFn } from "./prehash.js";
|
|
6
|
+
|
|
7
|
+
export const sign = async (
|
|
8
|
+
data: Uint8Array,
|
|
9
|
+
keypair: Ed25519Keypair,
|
|
10
|
+
prehash: PreHash
|
|
11
|
+
) => {
|
|
12
|
+
const hashedData = await prehashFn(data, prehash);
|
|
13
|
+
|
|
14
|
+
if (!keypair.privateKey.keyObject) {
|
|
15
|
+
keypair.privateKey.keyObject = crypto.createPrivateKey({
|
|
16
|
+
format: "der",
|
|
17
|
+
type: "pkcs8",
|
|
18
|
+
key: toDER(keypair.privateKeyPublicKey, true),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return new SignatureWithKey({
|
|
22
|
+
prehash,
|
|
23
|
+
publicKey: keypair.publicKey,
|
|
24
|
+
signature: crypto.sign(null, hashedData, keypair.privateKey.keyObject),
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const verifySignatureEd25519 = async (
|
|
29
|
+
signature: SignatureWithKey,
|
|
30
|
+
data: Uint8Array
|
|
31
|
+
) => {
|
|
32
|
+
let res = false;
|
|
33
|
+
try {
|
|
34
|
+
const hashedData = await prehashFn(data, signature.prehash);
|
|
35
|
+
|
|
36
|
+
/* return crypto.verify(null, hashedData, publicKey.keyObject, signature); */ // Sodium seems faster
|
|
37
|
+
const verified = sodium.crypto_sign_verify_detached(
|
|
38
|
+
signature.signature,
|
|
39
|
+
hashedData,
|
|
40
|
+
(signature.publicKey as Ed25519PublicKey).publicKey
|
|
41
|
+
);
|
|
42
|
+
res = verified;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return res;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const DER_PREFIX = Buffer.from([
|
|
50
|
+
48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32,
|
|
51
|
+
]);
|
|
52
|
+
const ED25519_OID = Buffer.from([0x06, 0x03, 0x2b, 0x65, 0x70]);
|
|
53
|
+
const SEQUENCE_TAG = Buffer.from([0x30]); // Sequence tag
|
|
54
|
+
const BIT_TAG = Buffer.from([0x03]); // Bit tag
|
|
55
|
+
const ZERO_BIT_TAG = Buffer.from([0x00]); // Zero bit
|
|
56
|
+
function toDER(key: Uint8Array, p = false) {
|
|
57
|
+
if (p) {
|
|
58
|
+
return Buffer.concat([DER_PREFIX, key]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Ed25519's OID
|
|
62
|
+
const oid = ED25519_OID;
|
|
63
|
+
|
|
64
|
+
// Create a byte sequence containing the OID and key
|
|
65
|
+
const elements = Buffer.concat([
|
|
66
|
+
SEQUENCE_TAG,
|
|
67
|
+
Buffer.from([oid.length]),
|
|
68
|
+
oid,
|
|
69
|
+
BIT_TAG,
|
|
70
|
+
Buffer.from([key.length + 1]),
|
|
71
|
+
ZERO_BIT_TAG,
|
|
72
|
+
key,
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// Wrap up by creating a sequence of elements
|
|
76
|
+
const der = Buffer.concat([
|
|
77
|
+
SEQUENCE_TAG,
|
|
78
|
+
Buffer.from([elements.length]),
|
|
79
|
+
elements,
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
return der;
|
|
83
|
+
}
|
package/src/ed25519.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { field, fixedArray, variant } from "@dao-xyz/borsh";
|
|
2
|
+
import { PrivateSignKey, PublicSignKey, Keypair } from "./key.js";
|
|
3
|
+
import { equals } from "@peerbit/uint8arrays";
|
|
4
|
+
import { Identity, Signer, SignWithKey } from "./signer.js";
|
|
5
|
+
import { SignatureWithKey } from "./signature.js";
|
|
6
|
+
import { toHexString } from "./utils.js";
|
|
7
|
+
import { peerIdFromKeys } from "@libp2p/peer-id";
|
|
8
|
+
import { supportedKeys } from "@libp2p/crypto/keys";
|
|
9
|
+
import { coerce } from "./bytes.js";
|
|
10
|
+
import sodium from "libsodium-wrappers";
|
|
11
|
+
import type { Ed25519PeerId, PeerId } from "@libp2p/interface-peer-id";
|
|
12
|
+
import { sign } from "./ed25519-sign.js";
|
|
13
|
+
import { PreHash } from "./prehash.js";
|
|
14
|
+
import { concat } from "uint8arrays";
|
|
15
|
+
|
|
16
|
+
@variant(0)
|
|
17
|
+
export class Ed25519PublicKey extends PublicSignKey {
|
|
18
|
+
@field({ type: fixedArray("u8", 32) })
|
|
19
|
+
publicKey: Uint8Array;
|
|
20
|
+
|
|
21
|
+
constructor(properties: { publicKey: Uint8Array }) {
|
|
22
|
+
super();
|
|
23
|
+
this.publicKey = properties.publicKey;
|
|
24
|
+
if (properties.publicKey.length !== 32) {
|
|
25
|
+
throw new Error("Expecting key to have length 32");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
equals(other: PublicSignKey): boolean {
|
|
30
|
+
if (other instanceof Ed25519PublicKey) {
|
|
31
|
+
return equals(this.publicKey, other.publicKey);
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
toString(): string {
|
|
36
|
+
return "ed25119p/" + toHexString(this.publicKey);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
toPeerId(): Promise<PeerId> {
|
|
40
|
+
return peerIdFromKeys(
|
|
41
|
+
new supportedKeys["ed25519"].Ed25519PublicKey(this.publicKey).bytes
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Don't use keyobject for publicKeys becuse it takes longer time to derive it compare to verifying with sodium
|
|
46
|
+
private keyObject: any;
|
|
47
|
+
get keyObject() {
|
|
48
|
+
return (
|
|
49
|
+
this._keyObject ||
|
|
50
|
+
(this._keyObject = crypto.createPublicKey({
|
|
51
|
+
format: "der",
|
|
52
|
+
type: "spki",
|
|
53
|
+
key: toDER(this.publicKey),
|
|
54
|
+
}))
|
|
55
|
+
);
|
|
56
|
+
} */
|
|
57
|
+
static fromPeerId(id: PeerId) {
|
|
58
|
+
if (!id.publicKey) {
|
|
59
|
+
throw new Error("Missing public key");
|
|
60
|
+
}
|
|
61
|
+
if (id.type === "Ed25519") {
|
|
62
|
+
return new Ed25519PublicKey({
|
|
63
|
+
publicKey: coerce(id.publicKey!.slice(4)),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
throw new Error("Unsupported key type: " + id.type);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@variant(0)
|
|
71
|
+
export class Ed25519PrivateKey extends PrivateSignKey {
|
|
72
|
+
@field({ type: fixedArray("u8", 32) })
|
|
73
|
+
privateKey: Uint8Array;
|
|
74
|
+
|
|
75
|
+
constructor(properties: { privateKey: Uint8Array }) {
|
|
76
|
+
super();
|
|
77
|
+
|
|
78
|
+
if (properties.privateKey.length !== 32) {
|
|
79
|
+
throw new Error("Expecting key to have length 32");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.privateKey = properties.privateKey;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
equals(other: Ed25519PrivateKey): boolean {
|
|
86
|
+
if (other instanceof Ed25519PrivateKey) {
|
|
87
|
+
return equals(this.privateKey, other.privateKey);
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
toString(): string {
|
|
93
|
+
return "ed25119s/" + toHexString(this.privateKey);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
keyObject: any; // crypto.KeyObject;
|
|
97
|
+
|
|
98
|
+
static fromPeerID(id: PeerId) {
|
|
99
|
+
if (!id.privateKey) {
|
|
100
|
+
throw new Error("Missing privateKey key");
|
|
101
|
+
}
|
|
102
|
+
if (id.type === "Ed25519") {
|
|
103
|
+
return new Ed25519PrivateKey({
|
|
104
|
+
privateKey: coerce(id.privateKey!.slice(4, 36)),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
throw new Error("Unsupported key type: " + id.type);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@variant(0)
|
|
112
|
+
export class Ed25519Keypair extends Keypair implements Identity {
|
|
113
|
+
@field({ type: Ed25519PublicKey })
|
|
114
|
+
publicKey: Ed25519PublicKey;
|
|
115
|
+
|
|
116
|
+
@field({ type: Ed25519PrivateKey })
|
|
117
|
+
privateKey: Ed25519PrivateKey;
|
|
118
|
+
|
|
119
|
+
constructor(properties: {
|
|
120
|
+
publicKey: Ed25519PublicKey;
|
|
121
|
+
privateKey: Ed25519PrivateKey;
|
|
122
|
+
}) {
|
|
123
|
+
super();
|
|
124
|
+
this.privateKey = properties.privateKey;
|
|
125
|
+
this.publicKey = properties.publicKey;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static async create(): Promise<Ed25519Keypair> {
|
|
129
|
+
await sodium.ready;
|
|
130
|
+
const generated = sodium.crypto_sign_keypair();
|
|
131
|
+
const kp = new Ed25519Keypair({
|
|
132
|
+
publicKey: new Ed25519PublicKey({
|
|
133
|
+
publicKey: generated.publicKey,
|
|
134
|
+
}),
|
|
135
|
+
privateKey: new Ed25519PrivateKey({
|
|
136
|
+
privateKey: generated.privateKey.slice(0, 32), // Only the private key part (?)
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return kp;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
sign(
|
|
144
|
+
data: Uint8Array,
|
|
145
|
+
prehash: PreHash = PreHash.NONE
|
|
146
|
+
): Promise<SignatureWithKey> {
|
|
147
|
+
return sign(data, this, prehash);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
signer(prehash: PreHash): SignWithKey {
|
|
151
|
+
return async (data: Uint8Array) => {
|
|
152
|
+
return this.sign(data, prehash);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
equals(other: Keypair) {
|
|
157
|
+
if (other instanceof Ed25519Keypair) {
|
|
158
|
+
return (
|
|
159
|
+
this.publicKey.equals(other.publicKey) &&
|
|
160
|
+
this.privateKey.equals(other.privateKey)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static fromPeerId(peerId: PeerId | Ed25519PeerId) {
|
|
167
|
+
return new Ed25519Keypair({
|
|
168
|
+
privateKey: Ed25519PrivateKey.fromPeerID(peerId),
|
|
169
|
+
publicKey: Ed25519PublicKey.fromPeerId(peerId),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
_privateKeyPublicKey: Uint8Array; // length 64
|
|
174
|
+
get privateKeyPublicKey(): Uint8Array {
|
|
175
|
+
return (
|
|
176
|
+
this._privateKeyPublicKey ||
|
|
177
|
+
(this._privateKeyPublicKey = concat([
|
|
178
|
+
this.privateKey.privateKey,
|
|
179
|
+
this.publicKey.publicKey,
|
|
180
|
+
]))
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
toPeerId(): Promise<PeerId> {
|
|
185
|
+
return peerIdFromKeys(
|
|
186
|
+
new supportedKeys["ed25519"].Ed25519PublicKey(this.publicKey.publicKey)
|
|
187
|
+
.bytes,
|
|
188
|
+
new supportedKeys["ed25519"].Ed25519PrivateKey(
|
|
189
|
+
this.privateKeyPublicKey,
|
|
190
|
+
this.publicKey.publicKey
|
|
191
|
+
).bytes
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
export * from "./errors.js";
|
|
2
|
+
import {
|
|
3
|
+
AbstractType,
|
|
4
|
+
deserialize,
|
|
5
|
+
field,
|
|
6
|
+
serialize,
|
|
7
|
+
variant,
|
|
8
|
+
vec,
|
|
9
|
+
} from "@dao-xyz/borsh";
|
|
10
|
+
import { equals } from "@peerbit/uint8arrays";
|
|
11
|
+
import { AccessError } from "./errors.js";
|
|
12
|
+
import sodium from "libsodium-wrappers";
|
|
13
|
+
import { X25519Keypair, X25519PublicKey, X25519SecretKey } from "./x25519.js";
|
|
14
|
+
import { Ed25519Keypair, Ed25519PublicKey } from "./ed25519.js";
|
|
15
|
+
import { randomBytes } from "./random.js";
|
|
16
|
+
import { Keychain } from "./keychain.js";
|
|
17
|
+
|
|
18
|
+
const NONCE_LENGTH = 24;
|
|
19
|
+
|
|
20
|
+
@variant(0)
|
|
21
|
+
export abstract class MaybeEncrypted<T> {
|
|
22
|
+
/**
|
|
23
|
+
* Will throw error if not decrypted
|
|
24
|
+
*/
|
|
25
|
+
get decrypted(): DecryptedThing<T> {
|
|
26
|
+
throw new Error("Not implented");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
decrypt(
|
|
30
|
+
keyOrKeychain?: Keychain | X25519Keypair
|
|
31
|
+
): Promise<DecryptedThing<T>> | DecryptedThing<T> {
|
|
32
|
+
throw new Error("Not implemented");
|
|
33
|
+
}
|
|
34
|
+
equals(other: MaybeEncrypted<T>): boolean {
|
|
35
|
+
throw new Error("Not implemented");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Clear cached data
|
|
40
|
+
*/
|
|
41
|
+
clear() {
|
|
42
|
+
throw new Error("Not implemented");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
abstract get byteLength(): number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@variant(0)
|
|
49
|
+
export class DecryptedThing<T> extends MaybeEncrypted<T> {
|
|
50
|
+
@field({ type: Uint8Array })
|
|
51
|
+
_data?: Uint8Array;
|
|
52
|
+
|
|
53
|
+
constructor(props?: { data?: Uint8Array; value?: T }) {
|
|
54
|
+
super();
|
|
55
|
+
if (props) {
|
|
56
|
+
this._data = props.data;
|
|
57
|
+
this._value = props.value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_value?: T;
|
|
62
|
+
getValue(clazz: AbstractType<T>): T {
|
|
63
|
+
if (this._value) {
|
|
64
|
+
return this._value;
|
|
65
|
+
}
|
|
66
|
+
if (!this._data) {
|
|
67
|
+
throw new Error("Missing data");
|
|
68
|
+
}
|
|
69
|
+
return deserialize(this._data, clazz);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async encrypt(
|
|
73
|
+
x25519Keypair: X25519Keypair,
|
|
74
|
+
...recieverPublicKeys: (X25519PublicKey | Ed25519PublicKey)[]
|
|
75
|
+
): Promise<EncryptedThing<T>> {
|
|
76
|
+
const bytes = serialize(this);
|
|
77
|
+
const epheremalKey = sodium.crypto_secretbox_keygen();
|
|
78
|
+
const nonce = randomBytes(NONCE_LENGTH); // crypto random is faster than sodim random
|
|
79
|
+
const cipher = sodium.crypto_secretbox_easy(bytes, nonce, epheremalKey);
|
|
80
|
+
|
|
81
|
+
const recieverX25519PublicKeys = await Promise.all(
|
|
82
|
+
recieverPublicKeys.map((key) => {
|
|
83
|
+
if (key instanceof Ed25519PublicKey) {
|
|
84
|
+
return X25519PublicKey.from(key);
|
|
85
|
+
}
|
|
86
|
+
return key;
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const ks = recieverX25519PublicKeys.map((recieverPublicKey) => {
|
|
91
|
+
const kNonce = randomBytes(NONCE_LENGTH); // crypto random is faster than sodium random
|
|
92
|
+
return new K({
|
|
93
|
+
encryptedKey: new CipherWithNonce({
|
|
94
|
+
cipher: sodium.crypto_box_easy(
|
|
95
|
+
epheremalKey,
|
|
96
|
+
kNonce,
|
|
97
|
+
recieverPublicKey.publicKey,
|
|
98
|
+
x25519Keypair.secretKey.secretKey
|
|
99
|
+
),
|
|
100
|
+
nonce: kNonce,
|
|
101
|
+
}),
|
|
102
|
+
recieverPublicKey,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const enc = new EncryptedThing<T>({
|
|
107
|
+
encrypted: new Uint8Array(cipher),
|
|
108
|
+
nonce,
|
|
109
|
+
envelope: new Envelope({
|
|
110
|
+
senderPublicKey: x25519Keypair.publicKey,
|
|
111
|
+
ks,
|
|
112
|
+
}),
|
|
113
|
+
});
|
|
114
|
+
enc._decrypted = this;
|
|
115
|
+
return enc;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get decrypted(): DecryptedThing<T> {
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
decrypt(): DecryptedThing<T> {
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
equals(other: MaybeEncrypted<T>) {
|
|
127
|
+
if (other instanceof DecryptedThing) {
|
|
128
|
+
return equals(this._data, other._data);
|
|
129
|
+
} else {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
clear() {
|
|
135
|
+
this._value = undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
get byteLength() {
|
|
139
|
+
return this._data!.byteLength;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@variant(0)
|
|
144
|
+
export class CipherWithNonce {
|
|
145
|
+
@field({ type: Uint8Array })
|
|
146
|
+
nonce: Uint8Array;
|
|
147
|
+
|
|
148
|
+
@field({ type: Uint8Array })
|
|
149
|
+
cipher: Uint8Array;
|
|
150
|
+
|
|
151
|
+
constructor(props?: { nonce: Uint8Array; cipher: Uint8Array }) {
|
|
152
|
+
if (props) {
|
|
153
|
+
this.nonce = props.nonce;
|
|
154
|
+
this.cipher = props.cipher;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
equals(other: CipherWithNonce): boolean {
|
|
159
|
+
if (other instanceof CipherWithNonce) {
|
|
160
|
+
return (
|
|
161
|
+
equals(this.nonce, other.nonce) && equals(this.cipher, other.cipher)
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@variant(0)
|
|
170
|
+
export class K {
|
|
171
|
+
@field({ type: CipherWithNonce })
|
|
172
|
+
_encryptedKey: CipherWithNonce;
|
|
173
|
+
|
|
174
|
+
@field({ type: X25519PublicKey })
|
|
175
|
+
_recieverPublicKey: X25519PublicKey;
|
|
176
|
+
|
|
177
|
+
constructor(props?: {
|
|
178
|
+
encryptedKey: CipherWithNonce;
|
|
179
|
+
recieverPublicKey: X25519PublicKey;
|
|
180
|
+
}) {
|
|
181
|
+
if (props) {
|
|
182
|
+
this._encryptedKey = props.encryptedKey;
|
|
183
|
+
this._recieverPublicKey = props.recieverPublicKey;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
equals(other: K): boolean {
|
|
188
|
+
if (other instanceof K) {
|
|
189
|
+
return (
|
|
190
|
+
this._encryptedKey.equals(other._encryptedKey) &&
|
|
191
|
+
this._recieverPublicKey.equals(other._recieverPublicKey)
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@variant(0)
|
|
200
|
+
export class Envelope {
|
|
201
|
+
@field({ type: X25519PublicKey })
|
|
202
|
+
_senderPublicKey: X25519PublicKey;
|
|
203
|
+
|
|
204
|
+
@field({ type: vec(K) })
|
|
205
|
+
_ks: K[];
|
|
206
|
+
|
|
207
|
+
constructor(props?: { senderPublicKey: X25519PublicKey; ks: K[] }) {
|
|
208
|
+
if (props) {
|
|
209
|
+
this._senderPublicKey = props.senderPublicKey;
|
|
210
|
+
this._ks = props.ks;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
equals(other: Envelope): boolean {
|
|
215
|
+
if (other instanceof Envelope) {
|
|
216
|
+
if (!this._senderPublicKey.equals(other._senderPublicKey)) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (this._ks.length !== other._ks.length) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
for (let i = 0; i < this._ks.length; i++) {
|
|
224
|
+
if (!this._ks[i].equals(other._ks[i])) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
} else {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@variant(1)
|
|
236
|
+
export class EncryptedThing<T> extends MaybeEncrypted<T> {
|
|
237
|
+
@field({ type: Uint8Array })
|
|
238
|
+
_encrypted: Uint8Array;
|
|
239
|
+
|
|
240
|
+
@field({ type: Uint8Array })
|
|
241
|
+
_nonce: Uint8Array;
|
|
242
|
+
|
|
243
|
+
@field({ type: Envelope })
|
|
244
|
+
_envelope: Envelope;
|
|
245
|
+
|
|
246
|
+
constructor(props?: {
|
|
247
|
+
encrypted: Uint8Array;
|
|
248
|
+
nonce: Uint8Array;
|
|
249
|
+
envelope: Envelope;
|
|
250
|
+
}) {
|
|
251
|
+
super();
|
|
252
|
+
if (props) {
|
|
253
|
+
this._encrypted = props.encrypted;
|
|
254
|
+
this._nonce = props.nonce;
|
|
255
|
+
this._envelope = props.envelope;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_decrypted?: DecryptedThing<T>;
|
|
260
|
+
get decrypted(): DecryptedThing<T> {
|
|
261
|
+
if (!this._decrypted) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
"Entry has not been decrypted, invoke decrypt method before"
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return this._decrypted;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async decrypt(
|
|
270
|
+
keyResolver?: Keychain | X25519Keypair
|
|
271
|
+
): Promise<DecryptedThing<T>> {
|
|
272
|
+
if (this._decrypted) {
|
|
273
|
+
return this._decrypted;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!keyResolver) {
|
|
277
|
+
throw new AccessError("Expecting key resolver");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// We only need to open with one of the keys
|
|
281
|
+
let key: { index: number; keypair: X25519Keypair } | undefined;
|
|
282
|
+
if (keyResolver instanceof X25519Keypair) {
|
|
283
|
+
for (const [i, k] of this._envelope._ks.entries()) {
|
|
284
|
+
if (k._recieverPublicKey.equals(keyResolver.publicKey)) {
|
|
285
|
+
key = {
|
|
286
|
+
index: i,
|
|
287
|
+
keypair: keyResolver,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
for (const [i, k] of this._envelope._ks.entries()) {
|
|
293
|
+
const exported = await keyResolver.exportByKey(k._recieverPublicKey);
|
|
294
|
+
if (exported) {
|
|
295
|
+
key = {
|
|
296
|
+
index: i,
|
|
297
|
+
keypair: exported,
|
|
298
|
+
};
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (key) {
|
|
305
|
+
const k = this._envelope._ks[key.index];
|
|
306
|
+
let secretKey: X25519SecretKey = undefined as any;
|
|
307
|
+
if (key.keypair instanceof X25519Keypair) {
|
|
308
|
+
secretKey = key.keypair.secretKey;
|
|
309
|
+
} else {
|
|
310
|
+
secretKey = await X25519SecretKey.from(key.keypair);
|
|
311
|
+
}
|
|
312
|
+
let epheremalKey: Uint8Array;
|
|
313
|
+
try {
|
|
314
|
+
epheremalKey = sodium.crypto_box_open_easy(
|
|
315
|
+
k._encryptedKey.cipher,
|
|
316
|
+
k._encryptedKey.nonce,
|
|
317
|
+
this._envelope._senderPublicKey.publicKey,
|
|
318
|
+
secretKey.secretKey
|
|
319
|
+
);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
throw new AccessError("Failed to decrypt");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// TODO: is nested decryption necessary?
|
|
325
|
+
/* let der: any = this;
|
|
326
|
+
let counter = 0;
|
|
327
|
+
while (der instanceof EncryptedThing) {
|
|
328
|
+
const decrypted = await sodium.crypto_secretbox_open_easy(this._encrypted, this._nonce, epheremalKey);
|
|
329
|
+
der = deserialize(decrypted, DecryptedThing)
|
|
330
|
+
counter += 1;
|
|
331
|
+
if (counter >= 10) {
|
|
332
|
+
throw new Error("Unexpected decryption behaviour, data seems to always be in encrypted state")
|
|
333
|
+
}
|
|
334
|
+
} */
|
|
335
|
+
|
|
336
|
+
const der = deserialize(
|
|
337
|
+
sodium.crypto_secretbox_open_easy(
|
|
338
|
+
this._encrypted,
|
|
339
|
+
this._nonce,
|
|
340
|
+
epheremalKey
|
|
341
|
+
),
|
|
342
|
+
DecryptedThing
|
|
343
|
+
);
|
|
344
|
+
this._decrypted = der as DecryptedThing<T>;
|
|
345
|
+
} else {
|
|
346
|
+
throw new AccessError("Failed to resolve decryption key");
|
|
347
|
+
}
|
|
348
|
+
return this._decrypted;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
equals(other: MaybeEncrypted<T>): boolean {
|
|
352
|
+
if (other instanceof EncryptedThing) {
|
|
353
|
+
if (!equals(this._encrypted, other._encrypted)) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
if (!equals(this._nonce, other._nonce)) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!this._envelope.equals(other._envelope)) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
return true;
|
|
364
|
+
} else {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
clear() {
|
|
370
|
+
this._decrypted = undefined;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
get byteLength() {
|
|
374
|
+
return this._encrypted.byteLength; // ignore other metdata for now in the size calculation
|
|
375
|
+
}
|
|
376
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { toBase64 } from "./utils";
|
|
2
|
+
import { SHA256 } from "@stablelib/sha256";
|
|
3
|
+
export const sha256Base64 = async (bytes: Uint8Array): Promise<string> =>
|
|
4
|
+
toBase64(await sha256(bytes));
|
|
5
|
+
export const sha256Base64Sync = (bytes: Uint8Array): string =>
|
|
6
|
+
toBase64(new SHA256().update(bytes).digest());
|
|
7
|
+
export const sha256 = async (bytes: Uint8Array): Promise<Uint8Array> =>
|
|
8
|
+
new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", bytes));
|
|
9
|
+
export const sha256Sync = (bytes: Uint8Array): Uint8Array =>
|
|
10
|
+
new SHA256().update(bytes).digest();
|
package/src/hash.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
|
|
3
|
+
export const sha256Base64Sync = (bytes: Uint8Array): string =>
|
|
4
|
+
crypto.createHash("sha256").update(bytes).digest("base64");
|
|
5
|
+
export const sha256Base64 = async (bytes: Uint8Array): Promise<string> =>
|
|
6
|
+
crypto.createHash("sha256").update(bytes).digest("base64");
|
|
7
|
+
export const sha256 = async (bytes: Uint8Array): Promise<Uint8Array> =>
|
|
8
|
+
crypto.createHash("sha256").update(bytes).digest();
|
|
9
|
+
export const sha256Sync = (bytes: Uint8Array): Uint8Array =>
|
|
10
|
+
crypto.createHash("sha256").update(bytes).digest();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./key.js";
|
|
2
|
+
export * from "./ed25519.js";
|
|
3
|
+
export * from "./signature.js";
|
|
4
|
+
export * from "./key.js";
|
|
5
|
+
export * from "./sepc256k1.js";
|
|
6
|
+
export * from "./x25519.js";
|
|
7
|
+
export * from "./encryption.js";
|
|
8
|
+
export * from "./libp2p.js";
|
|
9
|
+
export * from "./utils.js";
|
|
10
|
+
export * from "./hash.js";
|
|
11
|
+
export * from "./random.js";
|
|
12
|
+
export * from "./prehash.js";
|
|
13
|
+
export * from "./signer.js";
|
|
14
|
+
export * from "./keychain.js";
|