Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring src of sdk-core-wallet with history #490

Merged
merged 140 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
058e93a
Sketch mnemonic class.
andreibancioiu Dec 3, 2020
4fe3533
Sketch private key class.
andreibancioiu Dec 3, 2020
fe7e0ef
Compute public keys.
andreibancioiu Dec 3, 2020
4b7344d
WIP - Migrate toKeyFileObject.
andreibancioiu Dec 3, 2020
c192bb4
JSON keyfiles - generate and load. Tests.
andreibancioiu Dec 7, 2020
7afc3b7
Bit of benchmarking for scryptsy.
andreibancioiu Dec 9, 2020
661d6a3
Sketch BLS migration.
andreibancioiu Dec 9, 2020
77d7244
Fix, test signing.
andreibancioiu Jan 11, 2021
df33d2c
Rename files, re-group.
andreibancioiu Jan 11, 2021
eb22b24
PEM files. Signer components.
andreibancioiu Jan 11, 2021
70eb4a2
Rename private -> secret.
andreibancioiu Jan 11, 2021
b48994b
Add tests, fix tests.
andreibancioiu Jan 11, 2021
38c145a
Fix after merge.
andreibancioiu Jan 12, 2021
676b984
Fix after review, part 1.
andreibancioiu Jan 15, 2021
6748803
Fix after review, part 2.
andreibancioiu Jan 15, 2021
1b7bd12
Merge branch 'development' into abi
andreibancioiu Jan 28, 2021
66760e4
Add test for signing using PEM file.
andreibancioiu Feb 10, 2021
a6478f8
Extra checks, error handling and tests for PEM parsing etc.
andreibancioiu Feb 10, 2021
937001e
Make tweetnacl use Uint8Array
arhtudormorar Feb 10, 2021
f369b66
Fixes necessary for automatic publishing of elrond-sdk-docs (#201)
andreibancioiu Feb 17, 2021
0f4115e
Merge branch 'development' into abi
andreibancioiu Feb 17, 2021
fc88864
Fix some tests upon merge.
andreibancioiu Feb 17, 2021
2607ddc
Fix after review.
andreibancioiu Mar 10, 2021
e7344a6
implemented encryption component
ccorcoveanu Jun 18, 2021
702268d
implemented encryption component
ccorcoveanu Jun 18, 2021
77f3306
add json decoed function for EncryptedData
ccorcoveanu Jun 22, 2021
1145192
Add contract wrapper
claudiu725 Jun 28, 2021
1f93e0d
added empty line
ccorcoveanu Jun 29, 2021
a4b511e
added empty line
ccorcoveanu Jun 29, 2021
a0950fc
typo fix
ccorcoveanu Jul 1, 2021
db62fd4
Merge pull request #14 from ElrondNetwork/development
ccorcoveanu Jul 5, 2021
afb67aa
Merge pull request #20 from ElrondNetwork/feat/nft-token
claudiulataretu Jul 8, 2021
fb1a364
Merge pull request #22 from ElrondNetwork/development
claudiulataretu Jul 8, 2021
0cd3700
message signing progress
ccorcoveanu Jul 15, 2021
cf57740
finished message signing implementation
ccorcoveanu Jul 15, 2021
912d1d2
Merge pull request #30 from ElrondNetwork/development
ccorcoveanu Jul 16, 2021
57c6a7a
exported user verifier
ccorcoveanu Jul 20, 2021
b4e1672
Merge pull request #31 from ElrondNetwork/bugfix-export-user-verifier
ccorcoveanu Jul 20, 2021
10b89b8
Merge pull request #32 from ElrondNetwork/development
ccorcoveanu Jul 20, 2021
47c52c7
Merge pull request #33 from ElrondNetwork/login-token
ccorcoveanu Jul 21, 2021
59b0a9b
Merge pull request #36 from ElrondNetwork/development
ccorcoveanu Jul 21, 2021
01b1e66
Merge commit '3e3db61a69e75abce880141545a12b0f0a1cb85a' into contract…
claudiu725 Jul 28, 2021
e4e5d04
Merge pull request #9 from ElrondNetwork/contract-wrapper
claudiu725 Aug 9, 2021
afeacb6
Merge branch 'development' of github.com:ElrondNetwork/elrond-sdk-erd…
radumojic Aug 11, 2021
894bbda
Merge pull request #50 from ElrondNetwork/walletconnect-provider-toke…
ccorcoveanu Aug 11, 2021
58bc47e
Fix remaining imports.
andreibancioiu Mar 14, 2022
5eb84e4
Merge remote-tracking branch 'temp/main' into copy-crypto-folder
andreibancioiu Mar 23, 2022
8ca8d3c
Merge pull request #1 from ElrondNetwork/copy-crypto-folder
andreibancioiu Mar 23, 2022
b2d88da
Partial fixes after extraction.
andreibancioiu Mar 23, 2022
dc389ca
Fix tests upon extraction. Implement dummy test doubles.
andreibancioiu Mar 23, 2022
a24eeea
Fix browser tests.
andreibancioiu Mar 23, 2022
cdc7b74
Fix after self-review.
andreibancioiu Mar 23, 2022
336a46c
Fix after review.
andreibancioiu Mar 25, 2022
6058931
Merge pull request #2 from ElrondNetwork/fixes-after-extraction
andreibancioiu Mar 25, 2022
651277a
encryptor partial implementation
ccorcoveanu Aug 11, 2022
bc5089a
finished encryptor
ccorcoveanu Aug 12, 2022
04b0d01
add decryptor and basic unit tests
ccorcoveanu Aug 16, 2022
0f213d9
add empty line
ccorcoveanu Aug 16, 2022
c6e1cfd
fix after review
ccorcoveanu Aug 16, 2022
650ac40
fix after review
ccorcoveanu Aug 24, 2022
b48d3b8
implement 'half deterministic nonce'
ccorcoveanu Aug 24, 2022
e414dea
Merge pull request #8 from ElrondNetwork/x25519-encryption
ccorcoveanu Aug 24, 2022
5b31179
fixed changelog, export encryptor/decryptor and prepared release
ccorcoveanu Aug 24, 2022
bb4c079
Merge pull request #9 from ElrondNetwork/bump-version
ccorcoveanu Aug 24, 2022
b0cb66d
added guardianSigner
schimih Sep 12, 2022
a96e5ca
minor fix
schimih Sep 12, 2022
7efe921
Merge branch 'main' into guardianSigner
schimih Sep 12, 2022
88c5f91
fixes after review
schimih Oct 19, 2022
201be39
fix
schimih Oct 19, 2022
dac65bc
changed `guardian.sign()` to `guardian.guard()`
schimih Oct 19, 2022
ec31e5e
fixes after review
schimih Oct 19, 2022
1ab3c69
Elrond to MultiversX (part 1).
andreibancioiu Jan 6, 2023
2538954
Merge pull request #15 from multiversx/rebranding-01
andreibancioiu Jan 9, 2023
6e2f2c3
Rename package: sdk-wallet.
andreibancioiu Jan 11, 2023
006991e
Merge pull request #16 from multiversx/rebranding-03
andreibancioiu Jan 11, 2023
570dfce
Use sdk-bls-wasm from new organization.
andreibancioiu Jan 11, 2023
8029c43
Merge pull request #17 from multiversx/reference-bls-wasm
andreibancioiu Jan 11, 2023
a988993
Sketch wallet.json with mnemonic / arbitrary data.
andreibancioiu Jan 26, 2023
06b8a8c
Merge branch 'main' into guardianSigner
schimih Jan 26, 2023
847c733
Test for shorter mnemonics (12 words).
andreibancioiu Jan 30, 2023
baf215e
Add version checks, add unit tests.
andreibancioiu Jan 30, 2023
6f16027
Fix after review (point 4).
andreibancioiu Feb 1, 2023
1577e46
Revert version-related changes. Keystore version remains v4.
andreibancioiu Feb 2, 2023
645a218
Extra type safety.
andreibancioiu Feb 2, 2023
ec013a0
Fix after self-review.
andreibancioiu Feb 2, 2023
f7e646a
Merge pull request #19 from multiversx/wallet-v5
andreibancioiu Feb 6, 2023
2e1c1ed
Merge branch 'main' into guardianSigner
andreibancioiu Feb 22, 2023
7cdc80b
Do not handle user's signature in GuardianSigner (simplification).
andreibancioiu Feb 22, 2023
7ad21ff
Attempt to avoid breaking change.
andreibancioiu Feb 23, 2023
c71d332
Drop some interfaces.
andreibancioiu Feb 23, 2023
0f6f76a
Simplify test object.
andreibancioiu Feb 23, 2023
11a1ea7
Merge pull request #20 from multiversx/drop-some-interfaces
andreibancioiu Feb 23, 2023
dd6c529
Simplified Verify(), to receive the raw data and the signature.
andreibancioiu Feb 24, 2023
b6294e7
Remove interfaces: ISigner, ISignable. Now, signer.sign() returns the…
andreibancioiu Feb 24, 2023
ec6b3a3
Fix after self-review.
andreibancioiu Feb 24, 2023
7443453
Merge pull request #22 from multiversx/verify-24
andreibancioiu Feb 24, 2023
efd160d
Merge pull request #23 from multiversx/simplify-interface
andreibancioiu Feb 24, 2023
413ba47
Merge branch 'feat/next' into guardianSigner
andreibancioiu Feb 24, 2023
802f7a0
Merge pull request #10 from multiversx/guardianSigner
andreibancioiu Feb 27, 2023
b15f6d1
Bit of decoupling.
andreibancioiu Mar 13, 2023
c73b739
Merge pull request #25 from multiversx/next-13
andreibancioiu Mar 21, 2023
19dc566
@noble/ed25519 for sign & verify (user wallets).
andreibancioiu Mar 21, 2023
88bc18d
Add benchmark for sign & verify (before switching to noble crypto libs).
andreibancioiu Mar 21, 2023
98d9db4
Merge pull request #26 from multiversx/benchmark-sign-verify
andreibancioiu Mar 21, 2023
38a9176
Merge branch 'feat/next' into noble-ed25519
andreibancioiu Mar 21, 2023
5dc401e
Fix after self-review.
andreibancioiu Mar 21, 2023
486ae63
Merge pull request #27 from multiversx/noble-ed25519
andreibancioiu Mar 22, 2023
a84cb3d
Merge pull request #24 from multiversx/feat/next
andreibancioiu Mar 22, 2023
8c341c2
Automatically handle both kinds of keystores in "UserWallet.loadSecre…
andreibancioiu May 8, 2023
67b19c8
Add extra unit test.
andreibancioiu May 8, 2023
19d1426
Fix after review.
andreibancioiu May 9, 2023
203f63a
Merge pull request #39 from multiversx/handle-keystores
andreibancioiu May 9, 2023
322257a
Workaround: receive Uint8Array instead of Buffer.
andreibancioiu May 24, 2023
36d64fd
Reference buffer in globals (temporary workaround).
andreibancioiu May 24, 2023
c91a71d
Workaround on constructor.
andreibancioiu May 24, 2023
ad1bbe9
Fix get public key.
andreibancioiu May 24, 2023
351abcb
Add proper casts.
andreibancioiu May 24, 2023
45db24d
Merge pull request #40 from multiversx/uint8array-vs-buffer
andreibancioiu May 24, 2023
f46cc4c
add extra check
popenta Dec 6, 2023
87eb57d
remove only
popenta Dec 6, 2023
cc4d2e6
add extra assert for unit test
popenta Dec 6, 2023
9e797c5
add new line
popenta Dec 6, 2023
1366cf1
make error more explicit
popenta Dec 7, 2023
7470df5
Merge pull request #43 from multiversx/add-extra-check-decrypt-secret…
popenta Dec 7, 2023
bc60489
Remove Axios as dev-dependency.
andreibancioiu Jan 17, 2024
fe4d5f8
Merge pull request #44 from multiversx/dev-dependencies-2024-01-17
andreibancioiu Jan 18, 2024
f27408d
Accept "Uint8Array", in addition to "Buffer" on the main flows.
andreibancioiu Mar 21, 2024
dc50113
Merge pull request #47 from multiversx/next-core
andreibancioiu Mar 21, 2024
cac0eee
Sketch support for custom HRP.
andreibancioiu May 14, 2024
3b4400e
Add "newFromBech32", in addition to the legacy "fromBech32".
andreibancioiu May 14, 2024
ee264ef
Undo some changes, add "internal" markers, add some tests.
andreibancioiu May 14, 2024
01b9715
Deprecation notice.
andreibancioiu May 14, 2024
26ee76a
Add library config.
andreibancioiu May 15, 2024
5794b87
Merge pull request #48 from multiversx/custom-hrp
andreibancioiu May 27, 2024
5352a6d
Add mnemonicToEntropy and entropyToMnemonic.
andreibancioiu Oct 1, 2024
a5e0017
Handle some errors.
andreibancioiu Oct 1, 2024
2f74baa
Fix import.
andreibancioiu Oct 1, 2024
4fb5e5c
Fix imports.
andreibancioiu Oct 1, 2024
74bb771
Merge pull request #52 from multiversx/entropy-1
andreibancioiu Oct 1, 2024
8bc371c
Merge remote-tracking branch 'wallet/main' into merge-wallet
danielailie Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src-wallet/assertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ErrInvariantFailed } from "./errors";

export function guardLength(withLength: { length?: number }, expectedLength: number) {
let actualLength = withLength.length || 0;

if (actualLength != expectedLength) {
throw new ErrInvariantFailed(`wrong length, expected: ${expectedLength}, actual: ${actualLength}`);
}
}
15 changes: 15 additions & 0 deletions src-wallet/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Global configuration of the library.
*
* Generally speaking, this configuration should only be altered on exotic use cases;
* it can be seen as a collection of constants (or, to be more precise, rarely changed variables) that are used throughout the library.
*
* Never alter the configuration within a library!
* Only alter the configuration (if needed) within an (end) application that uses this library.
*/
export class LibraryConfig {
/**
* The human-readable-part of the bech32 addresses.
*/
public static DefaultAddressHrp: string = "erd";
}
8 changes: 8 additions & 0 deletions src-wallet/crypto/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const CipherAlgorithm = "aes-128-ctr";
export const DigestAlgorithm = "sha256";
export const KeyDerivationFunction = "scrypt";

// X25519 public key encryption
export const PubKeyEncVersion = 1;
export const PubKeyEncNonceLength = 24;
export const PubKeyEncCipher = "x25519-xsalsa20-poly1305";
27 changes: 27 additions & 0 deletions src-wallet/crypto/decryptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import crypto from "crypto";
import { EncryptedData } from "./encryptedData";
import { DigestAlgorithm } from "./constants";
import { Err } from "../errors";

export class Decryptor {
static decrypt(data: EncryptedData, password: string): Buffer {
const kdfparams = data.kdfparams;
const salt = Buffer.from(data.salt, "hex");
const iv = Buffer.from(data.iv, "hex");
const ciphertext = Buffer.from(data.ciphertext, "hex");
const derivedKey = kdfparams.generateDerivedKey(Buffer.from(password), salt);
const derivedKeyFirstHalf = derivedKey.slice(0, 16);
const derivedKeySecondHalf = derivedKey.slice(16, 32);

const computedMAC = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest();
const actualMAC = data.mac;

if (computedMAC.toString("hex") !== actualMAC) {
throw new Err("MAC mismatch, possibly wrong password");
}

const decipher = crypto.createDecipheriv(data.cipher, derivedKeyFirstHalf, iv);

return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
}
}
36 changes: 36 additions & 0 deletions src-wallet/crypto/derivationParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import scryptsy from "scryptsy";

export class ScryptKeyDerivationParams {
/**
* numIterations
*/
n = 4096;

/**
* memFactor
*/
r = 8;

/**
* pFactor
*/
p = 1;

dklen = 32;

constructor(n = 4096, r = 8, p = 1, dklen = 32) {
this.n = n;
this. r = r;
this.p = p;
this.dklen = dklen;
}

/**
* Will take about:
* - 80-90 ms in Node.js, on a i3-8100 CPU @ 3.60GHz
* - 350-360 ms in browser (Firefox), on a i3-8100 CPU @ 3.60GHz
*/
public generateDerivedKey(password: Buffer, salt: Buffer): Buffer {
return scryptsy(password, salt, this.n, this.r, this.p, this.dklen);
}
}
22 changes: 22 additions & 0 deletions src-wallet/crypto/encrypt.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { assert } from "chai";
import { Decryptor } from "./decryptor";
import { EncryptedData } from "./encryptedData";
import { Encryptor } from "./encryptor";

describe("test address", () => {
it("encrypts/decrypts", () => {
const sensitiveData = Buffer.from("my mnemonic");
const encryptedData = Encryptor.encrypt(sensitiveData, "password123");
const decryptedBuffer = Decryptor.decrypt(encryptedData, "password123");

assert.equal(sensitiveData.toString('hex'), decryptedBuffer.toString('hex'));
});

it("encodes/decodes kdfparams", () => {
const sensitiveData = Buffer.from("my mnemonic");
const encryptedData = Encryptor.encrypt(sensitiveData, "password123");
const decodedData = EncryptedData.fromJSON(encryptedData.toJSON());

assert.deepEqual(decodedData, encryptedData, "invalid decoded data");
});
});
65 changes: 65 additions & 0 deletions src-wallet/crypto/encryptedData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ScryptKeyDerivationParams } from "./derivationParams";

export class EncryptedData {
id: string;
version: number;
cipher: string;
ciphertext: string;
iv: string;
kdf: string;
kdfparams: ScryptKeyDerivationParams;
salt: string;
mac: string;

constructor(data: Omit<EncryptedData, "toJSON">) {
this.id = data.id;
this.version = data.version;
this.ciphertext = data.ciphertext;
this.iv = data.iv;
this.cipher = data.cipher;
this.kdf = data.kdf;
this.kdfparams = data.kdfparams;
this.mac = data.mac;
this.salt = data.salt;
}

toJSON(): any {
return {
version: this.version,
id: this.id,
crypto: {
ciphertext: this.ciphertext,
cipherparams: { iv: this.iv },
cipher: this.cipher,
kdf: this.kdf,
kdfparams: {
dklen: this.kdfparams.dklen,
salt: this.salt,
n: this.kdfparams.n,
r: this.kdfparams.r,
p: this.kdfparams.p
},
mac: this.mac,
}
};
}

static fromJSON(data: any): EncryptedData {
return new EncryptedData({
version: data.version,
id: data.id,
ciphertext: data.crypto.ciphertext,
iv: data.crypto.cipherparams.iv,
cipher: data.crypto.cipher,
kdf: data.crypto.kdf,
kdfparams: new ScryptKeyDerivationParams(
data.crypto.kdfparams.n,
data.crypto.kdfparams.r,
data.crypto.kdfparams.p,
data.crypto.kdfparams.dklen,
),
salt: data.crypto.kdfparams.salt,
mac: data.crypto.mac,
});
}
}
40 changes: 40 additions & 0 deletions src-wallet/crypto/encryptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import crypto from "crypto";
import { CipherAlgorithm, DigestAlgorithm, KeyDerivationFunction } from "./constants";
import { ScryptKeyDerivationParams } from "./derivationParams";
import { EncryptedData } from "./encryptedData";
import { Randomness } from "./randomness";

interface IRandomness {
id: string;
iv: Buffer;
salt: Buffer;
}

export enum EncryptorVersion {
V4 = 4,
}

export class Encryptor {
static encrypt(data: Buffer, password: string, randomness: IRandomness = new Randomness()): EncryptedData {
const kdParams = new ScryptKeyDerivationParams();
const derivedKey = kdParams.generateDerivedKey(Buffer.from(password), randomness.salt);
const derivedKeyFirstHalf = derivedKey.slice(0, 16);
const derivedKeySecondHalf = derivedKey.slice(16, 32);
const cipher = crypto.createCipheriv(CipherAlgorithm, derivedKeyFirstHalf, randomness.iv);

const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]);
const mac = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest();

return new EncryptedData({
version: EncryptorVersion.V4,
id: randomness.id,
ciphertext: ciphertext.toString('hex'),
iv: randomness.iv.toString('hex'),
cipher: CipherAlgorithm,
kdf: KeyDerivationFunction,
kdfparams: kdParams,
mac: mac.toString('hex'),
salt: randomness.salt.toString('hex')
});
}
}
7 changes: 7 additions & 0 deletions src-wallet/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from "./constants";
export * from "./encryptor";
export * from "./decryptor";
export * from "./pubkeyEncryptor";
export * from "./pubkeyDecryptor";
export * from "./encryptedData";
export * from "./randomness";
36 changes: 36 additions & 0 deletions src-wallet/crypto/pubkeyDecryptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import crypto from "crypto";
import nacl from "tweetnacl";
import ed2curve from "ed2curve";
import { X25519EncryptedData } from "./x25519EncryptedData";
import { UserPublicKey, UserSecretKey } from "../userKeys";

export class PubkeyDecryptor {
static decrypt(data: X25519EncryptedData, decryptorSecretKey: UserSecretKey): Buffer {
const ciphertext = Buffer.from(data.ciphertext, 'hex');
const edhPubKey = Buffer.from(data.identities.ephemeralPubKey, 'hex');
const originatorPubKeyBuffer = Buffer.from(data.identities.originatorPubKey, 'hex');
const originatorPubKey = new UserPublicKey(originatorPubKeyBuffer);

const authMessage = crypto.createHash('sha256').update(
Buffer.concat([ciphertext, edhPubKey])
).digest();

if (!originatorPubKey.verify(authMessage, Buffer.from(data.mac, 'hex'))) {
throw new Error("Invalid authentication for encrypted message originator");
}

const nonce = Buffer.from(data.nonce, 'hex');
const x25519Secret = ed2curve.convertSecretKey(decryptorSecretKey.valueOf());
const x25519EdhPubKey = ed2curve.convertPublicKey(edhPubKey);
if (x25519EdhPubKey === null) {
throw new Error("Could not convert ed25519 public key to x25519");
}

const decryptedMessage = nacl.box.open(ciphertext, nonce, x25519EdhPubKey, x25519Secret);
if (decryptedMessage === null) {
throw new Error("Failed authentication for given ciphertext");
}

return Buffer.from(decryptedMessage);
}
}
35 changes: 35 additions & 0 deletions src-wallet/crypto/pubkeyEncrypt.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { assert } from "chai";
import { loadTestWallet, TestWallet } from "../testutils/wallets";
import { PubkeyEncryptor } from "./pubkeyEncryptor";
import { UserPublicKey, UserSecretKey } from "../userKeys";
import { PubkeyDecryptor } from "./pubkeyDecryptor";
import { X25519EncryptedData } from "./x25519EncryptedData";

describe("test address", () => {
let alice: TestWallet, bob: TestWallet, carol: TestWallet;
const sensitiveData = Buffer.from("alice's secret text for bob");
let encryptedDataOfAliceForBob: X25519EncryptedData;

before(async () => {
alice = await loadTestWallet("alice");
bob = await loadTestWallet("bob");
carol = await loadTestWallet("carol");

encryptedDataOfAliceForBob = PubkeyEncryptor.encrypt(sensitiveData, new UserPublicKey(bob.address.pubkey()), new UserSecretKey(alice.secretKey));
});

it("encrypts/decrypts", () => {
const decryptedData = PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey));
assert.equal(sensitiveData.toString('hex'), decryptedData.toString('hex'));
});

it("fails for different originator", () => {
encryptedDataOfAliceForBob.identities.originatorPubKey = carol.address.hex();
assert.throws(() => PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey)), "Invalid authentication for encrypted message originator");
});

it("fails for different DH public key", () => {
encryptedDataOfAliceForBob.identities.ephemeralPubKey = carol.address.hex();
assert.throws(() => PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey)), "Invalid authentication for encrypted message originator");
});
});
47 changes: 47 additions & 0 deletions src-wallet/crypto/pubkeyEncryptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import crypto from "crypto";
import ed2curve from "ed2curve";
import nacl from "tweetnacl";
import { UserPublicKey, UserSecretKey } from "../userKeys";
import { PubKeyEncCipher, PubKeyEncNonceLength, PubKeyEncVersion } from "./constants";
import { X25519EncryptedData } from "./x25519EncryptedData";

export class PubkeyEncryptor {
static encrypt(data: Buffer, recipientPubKey: UserPublicKey, authSecretKey: UserSecretKey): X25519EncryptedData {
// create a new x25519 keypair that will be used for EDH
const edhPair = nacl.sign.keyPair();
const recipientDHPubKey = ed2curve.convertPublicKey(recipientPubKey.valueOf());
if (recipientDHPubKey === null) {
throw new Error("Could not convert ed25519 public key to x25519");
}
const edhConvertedSecretKey = ed2curve.convertSecretKey(edhPair.secretKey);

// For the nonce we use a random component and a deterministic one based on the message
// - this is so we won't completely rely on the random number generator
const nonceDeterministic = crypto.createHash('sha256').update(data).digest().slice(0, PubKeyEncNonceLength / 2);
const nonceRandom = nacl.randomBytes(PubKeyEncNonceLength / 2);
const nonce = Buffer.concat([nonceDeterministic, nonceRandom]);
const encryptedMessage = nacl.box(data, nonce, recipientDHPubKey, edhConvertedSecretKey);

// Note that the ciphertext is already authenticated for the ephemeral key - but we want it authenticated by
// the ed25519 key which the user interacts with. A signature over H(ciphertext | edhPubKey)
// would be enough
const authMessage = crypto.createHash('sha256').update(
Buffer.concat([encryptedMessage, edhPair.publicKey])
).digest();

const signature = authSecretKey.sign(authMessage);

return new X25519EncryptedData({
version: PubKeyEncVersion,
nonce: Buffer.from(nonce).toString('hex'),
cipher: PubKeyEncCipher,
ciphertext: Buffer.from(encryptedMessage).toString('hex'),
mac: signature.toString('hex'),
identities: {
recipient: recipientPubKey.hex(),
ephemeralPubKey: Buffer.from(edhPair.publicKey).toString('hex'),
originatorPubKey: authSecretKey.generatePublicKey().hex(),
}
});
}
}
15 changes: 15 additions & 0 deletions src-wallet/crypto/randomness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { utils } from "@noble/ed25519";
import { v4 as uuidv4 } from "uuid";
const crypto = require("crypto");

export class Randomness {
salt: Buffer;
iv: Buffer;
id: string;

constructor(init?: Partial<Randomness>) {
this.salt = init?.salt || Buffer.from(utils.randomBytes(32));
this.iv = init?.iv || Buffer.from(utils.randomBytes(16));
this.id = init?.id || uuidv4({ random: crypto.randomBytes(16) });
}
}
Loading
Loading