From 058e93aa40c02d03ec5aca7a01852c93f2dd6873 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Thu, 3 Dec 2020 15:21:27 +0200 Subject: [PATCH 001/118] Sketch mnemonic class. --- src-wallet/mnemonic.spec.ts | 21 +++++++++++++++ src-wallet/mnemonic.ts | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src-wallet/mnemonic.spec.ts create mode 100644 src-wallet/mnemonic.ts diff --git a/src-wallet/mnemonic.spec.ts b/src-wallet/mnemonic.spec.ts new file mode 100644 index 000000000..2abfb70ce --- /dev/null +++ b/src-wallet/mnemonic.spec.ts @@ -0,0 +1,21 @@ +import { assert } from "chai"; +import { Mnemonic } from "./mnemonic"; +import { TestWallets } from "../testutils"; + +describe("test mnemonic", () => { + let wallets = new TestWallets(); + + it("should generate mnemonic", () => { + let mnemonic = Mnemonic.generate(); + let words = mnemonic.getWords(); + assert.lengthOf(words, 24); + }); + + it("should derive keys", () => { + let mnemonic = Mnemonic.fromString(wallets.mnemonic); + + assert.equal(mnemonic.deriveKey(0).toString(), wallets.alice.privateKey); + assert.equal(mnemonic.deriveKey(1).toString(), wallets.bob.privateKey); + assert.equal(mnemonic.deriveKey(2).toString(), wallets.carol.privateKey); + }); +}); diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts new file mode 100644 index 000000000..43b77adc0 --- /dev/null +++ b/src-wallet/mnemonic.ts @@ -0,0 +1,52 @@ +import * as errors from "../errors"; +import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from "bip39"; +import { PrivateKey } from "./privateKey"; +import { derivePath } from "ed25519-hd-key"; + +const MNEMONIC_STRENGTH = 256; +const BIP44_DERIVATION_PREFIX = "m/44'/508'/0'/0'"; + +export class Mnemonic { + private readonly text: string; + + private constructor(text: string) { + this.text = text; + } + + static generate(): Mnemonic { + let text = generateMnemonic(MNEMONIC_STRENGTH); + return new Mnemonic(text); + } + + static fromString(text: string) { + text = text.trim(); + + Mnemonic.assertTextIsValid(text); + return new Mnemonic(text); + } + + private static assertTextIsValid(text: string) { + let isValid = validateMnemonic(text); + + if (!isValid) { + throw new errors.ErrWrongMnemonic(); + } + } + + // TODO: Question for review: @ccorcoveanu, accountIndex or addressIndex? + deriveKey(index: number = 0, password: string = ""): PrivateKey { + let seed = mnemonicToSeedSync(this.text, password); + let derivationPath = `${BIP44_DERIVATION_PREFIX}/${index}'`; + let derivationResult = derivePath(derivationPath, seed.toString("hex")); + let key = derivationResult.key; + return new PrivateKey(key); + } + + getWords(): string[] { + return this.text.split(" "); + } + + toString(): string { + return this.text; + } +} From 4fe35336f14d24d64d3cb63686b4fa95c6dd35e5 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Thu, 3 Dec 2020 15:22:18 +0200 Subject: [PATCH 002/118] Sketch private key class. --- src-wallet/privateKey.spec.ts | 22 ++++++++++++++++++++ src-wallet/privateKey.ts | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src-wallet/privateKey.spec.ts create mode 100644 src-wallet/privateKey.ts diff --git a/src-wallet/privateKey.spec.ts b/src-wallet/privateKey.spec.ts new file mode 100644 index 000000000..9908a1b97 --- /dev/null +++ b/src-wallet/privateKey.spec.ts @@ -0,0 +1,22 @@ +import * as errors from "../errors"; +import { assert } from "chai"; +import { TestWallets } from "../testutils"; +import { PrivateKey } from "./privateKey"; + +describe("test private key", () => { + let wallets = new TestWallets(); + + it("should create", () => { + let keyHex = wallets.alice.privateKey; + let fromBuffer = new PrivateKey(Buffer.from(keyHex, "hex")); + let fromHex = PrivateKey.fromString(keyHex); + + assert.equal(fromBuffer.toString(), keyHex); + assert.equal(fromHex.toString(), keyHex); + }); + + it("should throw error when invalid input", () => { + assert.throw(() => new PrivateKey(Buffer.alloc(42)), errors.ErrInvariantFailed); + assert.throw(() => PrivateKey.fromString("foobar"), errors.ErrInvariantFailed); + }); +}); diff --git a/src-wallet/privateKey.ts b/src-wallet/privateKey.ts new file mode 100644 index 000000000..f807bf16d --- /dev/null +++ b/src-wallet/privateKey.ts @@ -0,0 +1,38 @@ +import * as errors from "../errors"; +import { guardLength } from "../utils"; + +export class PrivateKey { + private readonly buffer: Buffer; + + constructor(value: Buffer) { + guardLength(value, 32); + + this.buffer = value; + } + + static fromString(value: string): PrivateKey { + guardLength(value, 64); + + let buffer = Buffer.from(value, "hex"); + return new PrivateKey(buffer); + } + + static fromKeyFileObject() { + + } + + static fromPEM() { + } + + toPEM() { + } + + toString(): string { + return this.buffer.toString("hex"); + } + + valueOf(): Buffer { + return this.buffer; + } +} + From fe7e0efeb642b0de19f452d63113c9584af35780 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Thu, 3 Dec 2020 20:21:24 +0200 Subject: [PATCH 003/118] Compute public keys. --- src-wallet/privateKey.spec.ts | 16 ++++++++++++++++ src-wallet/privateKey.ts | 19 ++++++++++++++----- src-wallet/publicKey.ts | 20 ++++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src-wallet/publicKey.ts diff --git a/src-wallet/privateKey.spec.ts b/src-wallet/privateKey.spec.ts index 9908a1b97..7a63560c1 100644 --- a/src-wallet/privateKey.spec.ts +++ b/src-wallet/privateKey.spec.ts @@ -15,6 +15,22 @@ describe("test private key", () => { assert.equal(fromHex.toString(), keyHex); }); + it("should compute public key (and address)", () => { + let privateKey: PrivateKey; + + privateKey = new PrivateKey(Buffer.from(wallets.alice.privateKey, "hex")); + assert.equal(privateKey.toPublicKey().toString(), wallets.alice.address.hex()); + assert.isTrue(privateKey.toPublicKey().toAddress().equals(wallets.alice.address)); + + privateKey = new PrivateKey(Buffer.from(wallets.bob.privateKey, "hex")); + assert.equal(privateKey.toPublicKey().toString(), wallets.bob.address.hex()); + assert.isTrue(privateKey.toPublicKey().toAddress().equals(wallets.bob.address)); + + privateKey = new PrivateKey(Buffer.from(wallets.carol.privateKey, "hex")); + assert.equal(privateKey.toPublicKey().toString(), wallets.carol.address.hex()); + assert.isTrue(privateKey.toPublicKey().toAddress().equals(wallets.carol.address)); + }); + it("should throw error when invalid input", () => { assert.throw(() => new PrivateKey(Buffer.alloc(42)), errors.ErrInvariantFailed); assert.throw(() => PrivateKey.fromString("foobar"), errors.ErrInvariantFailed); diff --git a/src-wallet/privateKey.ts b/src-wallet/privateKey.ts index f807bf16d..f5a25a15b 100644 --- a/src-wallet/privateKey.ts +++ b/src-wallet/privateKey.ts @@ -1,13 +1,15 @@ import * as errors from "../errors"; +import * as tweetnacl from "tweetnacl"; import { guardLength } from "../utils"; +import {PublicKey} from "./publicKey"; export class PrivateKey { private readonly buffer: Buffer; - - constructor(value: Buffer) { - guardLength(value, 32); + + constructor(buffer: Buffer) { + guardLength(buffer, 32); - this.buffer = value; + this.buffer = buffer; } static fromString(value: string): PrivateKey { @@ -27,6 +29,14 @@ export class PrivateKey { toPEM() { } + toPublicKey(): PublicKey { + // TODO: Question for review: @ccorcoveanu, @AdoAdoAdo, here we use "fromSeed" (instead of fromSecretKey, which wouldn't work on 32-byte private keys). + // TODO: Question for review: is this all right? + let keyPair = tweetnacl.sign.keyPair.fromSeed(this.buffer); + let buffer = Buffer.from(keyPair.publicKey); + return new PublicKey(buffer); + } + toString(): string { return this.buffer.toString("hex"); } @@ -35,4 +45,3 @@ export class PrivateKey { return this.buffer; } } - diff --git a/src-wallet/publicKey.ts b/src-wallet/publicKey.ts new file mode 100644 index 000000000..65bf95ee0 --- /dev/null +++ b/src-wallet/publicKey.ts @@ -0,0 +1,20 @@ +import { Address } from "../address"; +import { guardLength } from "../utils"; + +export class PublicKey { + private readonly buffer: Buffer; + + constructor(buffer: Buffer) { + guardLength(buffer, 32); + + this.buffer = buffer; + } + + toString(): string { + return this.buffer.toString("hex"); + } + + toAddress(): Address { + return new Address(this.buffer); + } +} From 4b7344dab158494aa179f3608c321c08693e8118 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Thu, 3 Dec 2020 20:42:53 +0200 Subject: [PATCH 004/118] WIP - Migrate toKeyFileObject. --- src-wallet/keyFilePayload.ts | 3 ++ src-wallet/privateKey.ts | 70 ++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 src-wallet/keyFilePayload.ts diff --git a/src-wallet/keyFilePayload.ts b/src-wallet/keyFilePayload.ts new file mode 100644 index 000000000..7e186478e --- /dev/null +++ b/src-wallet/keyFilePayload.ts @@ -0,0 +1,3 @@ +class KeyFilePayload { + +} diff --git a/src-wallet/privateKey.ts b/src-wallet/privateKey.ts index f5a25a15b..e54b1eedd 100644 --- a/src-wallet/privateKey.ts +++ b/src-wallet/privateKey.ts @@ -1,14 +1,18 @@ import * as errors from "../errors"; import * as tweetnacl from "tweetnacl"; import { guardLength } from "../utils"; -import {PublicKey} from "./publicKey"; +import { PublicKey } from "./publicKey"; +import nacl from "tweetnacl"; +const crypto = require("crypto"); +const uuid = require("uuid/v4"); +const scryptsy = require("scryptsy"); export class PrivateKey { private readonly buffer: Buffer; - + constructor(buffer: Buffer) { guardLength(buffer, 32); - + this.buffer = buffer; } @@ -19,6 +23,66 @@ export class PrivateKey { return new PrivateKey(buffer); } + /** + * WIP! This PR is not ready for review yet! + * + * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L76 + * Notes: adjustements (code refactoring, no change in logic), in terms of: + * - typing (since this is the TypeScript version) + * - error handling (in line with erdjs's error system) + * - references to crypto functions + * - references to object members + * + * Given a password, it will generate the contents for a file containing the current initialised account's private + * key, passed through a password based key derivation function. + */ + toKeyFileObject(password: string) { + let privateKey = this.buffer; + let publicKey = this.toPublicKey(); + + const salt = Buffer.from(nacl.randomBytes(32)); + const iv = Buffer.from(nacl.randomBytes(16)); + const [kdParams, derivedKey] = this.generateDerivedKey(password, salt); + const cipher = crypto.createCipheriv("aes-128-ctr", derivedKey.slice(0, 16), iv); + const ciphertext = Buffer.concat([cipher.update(privateKey), cipher.final()]); + + const mac = crypto.createHmac("sha256", derivedKey.slice(16, 32)) + .update(ciphertext) + .digest(); + + const id = uuid({ random: crypto.randomBytes(16) }); + + return { + version: 4, + id: id, + address: publicKey.toString(), + bech32: publicKey.toAddress().toString(), + crypto: { + ciphertext: ciphertext.toString("hex"), + cipherparams: { + iv: iv.toString("hex") + }, + cipher: "aes-128-ctr", + kdf: "scrypt", + kdfparams: kdParams, + mac: mac.toString("hex"), + } + }; + } + + private generateDerivedKey(password: string, salt: Buffer): [derivedKey: any, params: any] { + const kdParams = { + dklen: 32, + salt: salt.toString("hex"), + n: 4096, + r: 8, + p: 1, + }; + + const derivedKey = scryptsy(Buffer.from(password), salt, kdParams.n, kdParams.r, kdParams.p, kdParams.dklen); + return [derivedKey, kdParams]; + } + static fromKeyFileObject() { } From c192bb4bc8a67540971ebafcb6072d29b230027d Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 7 Dec 2020 17:26:34 +0200 Subject: [PATCH 005/118] JSON keyfiles - generate and load. Tests. --- src-wallet/encryptedKey.spec.ts | 54 +++++++++++ src-wallet/encryptedKey.ts | 157 ++++++++++++++++++++++++++++++++ src-wallet/keyFilePayload.ts | 3 - src-wallet/privateKey.ts | 69 -------------- src-wallet/publicKey.ts | 4 + 5 files changed, 215 insertions(+), 72 deletions(-) create mode 100644 src-wallet/encryptedKey.spec.ts create mode 100644 src-wallet/encryptedKey.ts delete mode 100644 src-wallet/keyFilePayload.ts diff --git a/src-wallet/encryptedKey.spec.ts b/src-wallet/encryptedKey.spec.ts new file mode 100644 index 000000000..23f138dcb --- /dev/null +++ b/src-wallet/encryptedKey.spec.ts @@ -0,0 +1,54 @@ +import { assert } from "chai"; +import { TestWallets } from "../testutils"; +import { PrivateKey } from "./privateKey"; +import { EncryptedKey, Randomness } from "./encryptedKey"; + +describe("test encrypted key file", () => { + let wallets = new TestWallets(); + let alice = wallets.alice; + let bob = wallets.bob; + let carol = wallets.carol; + let password = wallets.password; + + it("should create and load encrypted files", () => { + let alicePrivateKey = PrivateKey.fromString(alice.privateKey); + let bobPrivateKey = PrivateKey.fromString(bob.privateKey); + let carolPrivateKey = PrivateKey.fromString(carol.privateKey); + + let aliceKeyFile = new EncryptedKey(alicePrivateKey, password); + let bobKeyFile = new EncryptedKey(bobPrivateKey, password); + let carolKeyFile = new EncryptedKey(carolPrivateKey, password); + + assert.equal(aliceKeyFile.toJSON().bech32, alice.address.bech32()); + assert.equal(bobKeyFile.toJSON().bech32, bob.address.bech32()); + assert.equal(carolKeyFile.toJSON().bech32, carol.address.bech32()); + + assert.deepEqual(EncryptedKey.load(aliceKeyFile.toJSON(), password), alicePrivateKey); + assert.deepEqual(EncryptedKey.load(bobKeyFile.toJSON(), password), bobPrivateKey); + assert.deepEqual(EncryptedKey.load(carolKeyFile.toJSON(), password), carolPrivateKey); + + // With provided randomness, in order to reproduce our development wallets + + aliceKeyFile = new EncryptedKey(alicePrivateKey, password, new Randomness({ + id: alice.keyFileObject.id, + iv: Buffer.from(alice.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(alice.keyFileObject.crypto.kdfparams.salt, "hex") + })); + + bobKeyFile = new EncryptedKey(bobPrivateKey, password, new Randomness({ + id: bob.keyFileObject.id, + iv: Buffer.from(bob.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(bob.keyFileObject.crypto.kdfparams.salt, "hex") + })); + + carolKeyFile = new EncryptedKey(carolPrivateKey, password, new Randomness({ + id: carol.keyFileObject.id, + iv: Buffer.from(carol.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(carol.keyFileObject.crypto.kdfparams.salt, "hex") + })); + + assert.deepEqual(aliceKeyFile.toJSON(), alice.keyFileObject); + assert.deepEqual(bobKeyFile.toJSON(), bob.keyFileObject); + assert.deepEqual(carolKeyFile.toJSON(), carol.keyFileObject); + }); +}); diff --git a/src-wallet/encryptedKey.ts b/src-wallet/encryptedKey.ts new file mode 100644 index 000000000..16cb54b14 --- /dev/null +++ b/src-wallet/encryptedKey.ts @@ -0,0 +1,157 @@ +import { PrivateKey } from "./privateKey"; +import * as errors from "../errors"; +import nacl from "tweetnacl"; +import { PublicKey } from "./publicKey"; +const crypto = require("crypto"); +const uuid = require("uuid/v4"); +const scryptsy = require("scryptsy"); + +const Version = 4; +const CipherAlgorithm = "aes-128-ctr"; +const DigestAlgorithm = "sha256"; +const KeyDerivationFunction = "scrypt"; + +class ScryptKeyDerivationParams { + /** + * numIterations + */ + n = 4096; + + /** + * memFactor + */ + r = 8; + + /** + * pFactor + */ + p = 1; + + dklen = 32; +} + +export class Randomness { + salt: Buffer; + iv: Buffer; + id: string; + + constructor(init?: Partial) { + this.salt = init?.salt || Buffer.from(nacl.randomBytes(32)); + this.iv = init?.iv || Buffer.from(nacl.randomBytes(16)); + this.id = init?.id || uuid({ random: crypto.randomBytes(16) }); + } +} + +export class EncryptedKey { + private readonly publicKey: PublicKey; + private readonly randomness: Randomness; + private readonly ciphertext: Buffer; + private readonly mac: Buffer; + private readonly kdfparams: ScryptKeyDerivationParams; + + /** + * WIP! This PR is not ready for review yet! + * + * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L76 + * Notes: adjustements (code refactoring, no change in logic), in terms of: + * - typing (since this is the TypeScript version) + * - error handling (in line with erdjs's error system) + * - references to crypto functions + * - references to object members + * + * Given a password, it will generate the contents for a file containing the current initialised account's private + * key, passed through a password-based key derivation function (kdf). + */ + constructor(privateKey: PrivateKey, password: string, randomness: Randomness = new Randomness()) { + const kdParams = new ScryptKeyDerivationParams(); + const derivedKey = EncryptedKey.generateDerivedKey(Buffer.from(password), randomness.salt, kdParams); + const derivedKeyFirstHalf = derivedKey.slice(0, 16); + const derivedKeySecondHalf = derivedKey.slice(16, 32); + const cipher = crypto.createCipheriv(CipherAlgorithm, derivedKeyFirstHalf, randomness.iv); + + const text = Buffer.concat([privateKey.valueOf(), privateKey.toPublicKey().valueOf()]); + const ciphertext = Buffer.concat([cipher.update(text), cipher.final()]); + const mac = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest(); + + this.publicKey = privateKey.toPublicKey(); + this.randomness = randomness; + this.ciphertext = ciphertext; + this.mac = mac; + this.kdfparams = kdParams; + } + + /** + * WIP! This PR is not ready for review yet! + * + * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L42 + * Notes: adjustements (code refactoring, no change in logic), in terms of: + * - typing (since this is the TypeScript version) + * - error handling (in line with erdjs's error system) + * - references to crypto functions + * - references to object members + * + * From an encrypted keyfile, given the password, load the private key and the public key. + */ + static load(keyFileObject: any, password: string): PrivateKey { + const kdfparams = keyFileObject.crypto.kdfparams; + const salt = Buffer.from(kdfparams.salt, "hex"); + const iv = Buffer.from(keyFileObject.crypto.cipherparams.iv, "hex"); + const ciphertext = Buffer.from(keyFileObject.crypto.ciphertext, "hex"); + const derivedKey = EncryptedKey.generateDerivedKey(Buffer.from(password), salt, kdfparams); + const derivedKeyFirstHalf = derivedKey.slice(0, 16); + const derivedKeySecondHalf = derivedKey.slice(16, 32); + + const computedMAC = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest(); + const actualMAC = keyFileObject.crypto.mac; + + if (computedMAC.toString("hex") !== actualMAC) { + throw new errors.ErrWallet("MAC mismatch, possibly wrong password"); + } + + const decipher = crypto.createDecipheriv(keyFileObject.crypto.cipher, derivedKeyFirstHalf, iv); + + let text = Buffer.concat([decipher.update(ciphertext), decipher.final()]); + while (text.length < 32) { + let zeroPadding = Buffer.from([0x00]); + text = Buffer.concat([zeroPadding, text]); + } + + let seed = text.slice(0, 32); + return new PrivateKey(seed); + } + + // TODO: load() (static), then decrypt(password)... + + private static generateDerivedKey(password: Buffer, salt: Buffer, kdfparams: ScryptKeyDerivationParams): Buffer { + // Question for review: @ccorcoveanu, why not this implementation? + // https://nodejs.org/api/crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback + const derivedKey = scryptsy(password, salt, kdfparams.n, kdfparams.r, kdfparams.p, kdfparams.dklen); + return derivedKey; + } + + /** + * Converts the encrypted keyfile to plain JavaScript object. + */ + toJSON(): any { + return { + version: Version, + id: this.randomness.id, + address: this.publicKey.toString(), + bech32: this.publicKey.toAddress().toString(), + crypto: { + ciphertext: this.ciphertext.toString("hex"), + cipherparams: { iv: this.randomness.iv.toString("hex") }, + cipher: CipherAlgorithm, + kdf: KeyDerivationFunction, + kdfparams: { + dklen: this.kdfparams.dklen, + salt: this.randomness.salt.toString("hex"), + n: this.kdfparams.n, + r: this.kdfparams.r, + p: this.kdfparams.p + }, + mac: this.mac.toString("hex"), + } + }; + } +} diff --git a/src-wallet/keyFilePayload.ts b/src-wallet/keyFilePayload.ts deleted file mode 100644 index 7e186478e..000000000 --- a/src-wallet/keyFilePayload.ts +++ /dev/null @@ -1,3 +0,0 @@ -class KeyFilePayload { - -} diff --git a/src-wallet/privateKey.ts b/src-wallet/privateKey.ts index e54b1eedd..2922153de 100644 --- a/src-wallet/privateKey.ts +++ b/src-wallet/privateKey.ts @@ -1,11 +1,6 @@ -import * as errors from "../errors"; import * as tweetnacl from "tweetnacl"; import { guardLength } from "../utils"; import { PublicKey } from "./publicKey"; -import nacl from "tweetnacl"; -const crypto = require("crypto"); -const uuid = require("uuid/v4"); -const scryptsy = require("scryptsy"); export class PrivateKey { private readonly buffer: Buffer; @@ -23,70 +18,6 @@ export class PrivateKey { return new PrivateKey(buffer); } - /** - * WIP! This PR is not ready for review yet! - * - * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L76 - * Notes: adjustements (code refactoring, no change in logic), in terms of: - * - typing (since this is the TypeScript version) - * - error handling (in line with erdjs's error system) - * - references to crypto functions - * - references to object members - * - * Given a password, it will generate the contents for a file containing the current initialised account's private - * key, passed through a password based key derivation function. - */ - toKeyFileObject(password: string) { - let privateKey = this.buffer; - let publicKey = this.toPublicKey(); - - const salt = Buffer.from(nacl.randomBytes(32)); - const iv = Buffer.from(nacl.randomBytes(16)); - const [kdParams, derivedKey] = this.generateDerivedKey(password, salt); - const cipher = crypto.createCipheriv("aes-128-ctr", derivedKey.slice(0, 16), iv); - const ciphertext = Buffer.concat([cipher.update(privateKey), cipher.final()]); - - const mac = crypto.createHmac("sha256", derivedKey.slice(16, 32)) - .update(ciphertext) - .digest(); - - const id = uuid({ random: crypto.randomBytes(16) }); - - return { - version: 4, - id: id, - address: publicKey.toString(), - bech32: publicKey.toAddress().toString(), - crypto: { - ciphertext: ciphertext.toString("hex"), - cipherparams: { - iv: iv.toString("hex") - }, - cipher: "aes-128-ctr", - kdf: "scrypt", - kdfparams: kdParams, - mac: mac.toString("hex"), - } - }; - } - - private generateDerivedKey(password: string, salt: Buffer): [derivedKey: any, params: any] { - const kdParams = { - dklen: 32, - salt: salt.toString("hex"), - n: 4096, - r: 8, - p: 1, - }; - - const derivedKey = scryptsy(Buffer.from(password), salt, kdParams.n, kdParams.r, kdParams.p, kdParams.dklen); - return [derivedKey, kdParams]; - } - - static fromKeyFileObject() { - - } - static fromPEM() { } diff --git a/src-wallet/publicKey.ts b/src-wallet/publicKey.ts index 65bf95ee0..4f4cf1a12 100644 --- a/src-wallet/publicKey.ts +++ b/src-wallet/publicKey.ts @@ -17,4 +17,8 @@ export class PublicKey { toAddress(): Address { return new Address(this.buffer); } + + valueOf(): Buffer { + return this.buffer; + } } From 7afc3b7f8102f9348f75eda6555e1d9719884cbc Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 9 Dec 2020 09:27:33 +0200 Subject: [PATCH 006/118] Bit of benchmarking for scryptsy. --- src-wallet/encryptedKey.spec.ts | 8 +++++++- src-wallet/encryptedKey.ts | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src-wallet/encryptedKey.spec.ts b/src-wallet/encryptedKey.spec.ts index 23f138dcb..6dd2897b5 100644 --- a/src-wallet/encryptedKey.spec.ts +++ b/src-wallet/encryptedKey.spec.ts @@ -10,22 +10,28 @@ describe("test encrypted key file", () => { let carol = wallets.carol; let password = wallets.password; - it("should create and load encrypted files", () => { + it("should create and load encrypted files", function() { + this.timeout(10000); + let alicePrivateKey = PrivateKey.fromString(alice.privateKey); let bobPrivateKey = PrivateKey.fromString(bob.privateKey); let carolPrivateKey = PrivateKey.fromString(carol.privateKey); + console.time("encrypt"); let aliceKeyFile = new EncryptedKey(alicePrivateKey, password); let bobKeyFile = new EncryptedKey(bobPrivateKey, password); let carolKeyFile = new EncryptedKey(carolPrivateKey, password); + console.timeEnd("encrypt"); assert.equal(aliceKeyFile.toJSON().bech32, alice.address.bech32()); assert.equal(bobKeyFile.toJSON().bech32, bob.address.bech32()); assert.equal(carolKeyFile.toJSON().bech32, carol.address.bech32()); + console.time("decrypt"); assert.deepEqual(EncryptedKey.load(aliceKeyFile.toJSON(), password), alicePrivateKey); assert.deepEqual(EncryptedKey.load(bobKeyFile.toJSON(), password), bobPrivateKey); assert.deepEqual(EncryptedKey.load(carolKeyFile.toJSON(), password), carolPrivateKey); + console.timeEnd("decrypt"); // With provided randomness, in order to reproduce our development wallets diff --git a/src-wallet/encryptedKey.ts b/src-wallet/encryptedKey.ts index 16cb54b14..849f4f027 100644 --- a/src-wallet/encryptedKey.ts +++ b/src-wallet/encryptedKey.ts @@ -122,6 +122,13 @@ export class EncryptedKey { // TODO: load() (static), then decrypt(password)... + /** + * 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 + * + * TODO: Question for review: @ccorcoveanu, @AdoAdoAdo, is this all right? + */ private static generateDerivedKey(password: Buffer, salt: Buffer, kdfparams: ScryptKeyDerivationParams): Buffer { // Question for review: @ccorcoveanu, why not this implementation? // https://nodejs.org/api/crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback From 661d6a326678f29f6b518aeb50d2fbac9ff4565a Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 9 Dec 2020 14:28:12 +0200 Subject: [PATCH 007/118] Sketch BLS migration. --- src-wallet/validatorKey.spec.ts | 19 +++++++++++++++ src-wallet/validatorKey.ts | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src-wallet/validatorKey.spec.ts create mode 100644 src-wallet/validatorKey.ts diff --git a/src-wallet/validatorKey.spec.ts b/src-wallet/validatorKey.spec.ts new file mode 100644 index 000000000..e4eb357d6 --- /dev/null +++ b/src-wallet/validatorKey.spec.ts @@ -0,0 +1,19 @@ +import * as errors from "../errors"; +import { assert } from "chai"; +import { TestWallets } from "../testutils"; +import { BLS, ValidatorKey } from "./validatorKey"; + +describe("test validator key", () => { + let wallets = new TestWallets(); + + it("should create", async () => { + await BLS.initIfNecessary(); + let privateKey = Buffer.from(Buffer.from("N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw==", "base64").toString(), "hex"); + let key = new ValidatorKey(privateKey); + assert.equal(key.toString(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); + + privateKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); + key = new ValidatorKey(privateKey); + assert.equal(key.toString(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); + }); +}); diff --git a/src-wallet/validatorKey.ts b/src-wallet/validatorKey.ts new file mode 100644 index 000000000..c32c730a8 --- /dev/null +++ b/src-wallet/validatorKey.ts @@ -0,0 +1,41 @@ +import { guardLength } from "../utils"; + +const bls = require('@elrondnetwork/bls-wasm'); + +export class BLS { + private static isInitialized: boolean = false; + + static async initIfNecessary() { + if (BLS.isInitialized) { + return; + } + + await bls.init(bls.BLS12_381); + + BLS.isInitialized = true; + } +} + +export class ValidatorKey { + private readonly secretKey: any; + private readonly publicKey: any; + + constructor(buffer: Buffer) { + guardLength(buffer, 32); + + this.secretKey = new bls.SecretKey(); + this.secretKey.setLittleEndian(Uint8Array.from(buffer)); + this.publicKey = this.secretKey.getPublicKey(); + } + + sign(message: Buffer): Buffer { + let signatureObject = this.secretKey.sign(message); + let signature = signatureObject.serialize(); + return signature; + } + + toString(): string { + return Buffer.from(this.publicKey.serialize()).toString("hex"); + } +} + From 77d7244866188c1a6af53f980b17b1f319d6cdff Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 11 Jan 2021 14:15:48 +0200 Subject: [PATCH 008/118] Fix, test signing. --- src-wallet/validatorKey.spec.ts | 8 +++++++- src-wallet/validatorKey.ts | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src-wallet/validatorKey.spec.ts b/src-wallet/validatorKey.spec.ts index e4eb357d6..ea0ab634a 100644 --- a/src-wallet/validatorKey.spec.ts +++ b/src-wallet/validatorKey.spec.ts @@ -1,4 +1,3 @@ -import * as errors from "../errors"; import { assert } from "chai"; import { TestWallets } from "../testutils"; import { BLS, ValidatorKey } from "./validatorKey"; @@ -8,12 +7,19 @@ describe("test validator key", () => { it("should create", async () => { await BLS.initIfNecessary(); + let privateKey = Buffer.from(Buffer.from("N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw==", "base64").toString(), "hex"); let key = new ValidatorKey(privateKey); assert.equal(key.toString(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); + let signature = key.sign(Buffer.from("hello")); + assert.equal(signature.toString("hex"), "84fd0a3a9d4f1ea2d4b40c6da67f9b786284a1c3895b7253fec7311597cda3f757862bb0690a92a13ce612c33889fd86"); + privateKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); key = new ValidatorKey(privateKey); assert.equal(key.toString(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); + + signature = key.sign(Buffer.from("hello")); + assert.equal(signature.toString("hex"), "be2e593ff10899a2ee8e1d5c8094e36c9f48e04b87e129991ff09475808743e07bb41bf6e7bc1463fa554c4b46594b98"); }); }); diff --git a/src-wallet/validatorKey.ts b/src-wallet/validatorKey.ts index c32c730a8..97b0a9760 100644 --- a/src-wallet/validatorKey.ts +++ b/src-wallet/validatorKey.ts @@ -30,7 +30,7 @@ export class ValidatorKey { sign(message: Buffer): Buffer { let signatureObject = this.secretKey.sign(message); - let signature = signatureObject.serialize(); + let signature = Buffer.from(signatureObject.serialize()); return signature; } From df33d2cd196ee0a78d00d1803bc9e5567a5e64f8 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 11 Jan 2021 19:53:06 +0200 Subject: [PATCH 009/118] Rename files, re-group. --- src-wallet/encryptedKey.spec.ts | 60 ---------- src-wallet/mnemonic.spec.ts | 21 ---- src-wallet/mnemonic.ts | 6 +- src-wallet/privateKey.spec.ts | 38 ------ src-wallet/privateKey.ts | 42 ------- src-wallet/publicKey.ts | 24 ---- src-wallet/userKeys.ts | 68 +++++++++++ src-wallet/{encryptedKey.ts => userWallet.ts} | 94 +++++++-------- src-wallet/users.spec.ts | 110 ++++++++++++++++++ .../{validatorKey.ts => validatorKeys.ts} | 13 ++- ...alidatorKey.spec.ts => validators.spec.ts} | 16 ++- 11 files changed, 247 insertions(+), 245 deletions(-) delete mode 100644 src-wallet/encryptedKey.spec.ts delete mode 100644 src-wallet/mnemonic.spec.ts delete mode 100644 src-wallet/privateKey.spec.ts delete mode 100644 src-wallet/privateKey.ts delete mode 100644 src-wallet/publicKey.ts create mode 100644 src-wallet/userKeys.ts rename src-wallet/{encryptedKey.ts => userWallet.ts} (82%) create mode 100644 src-wallet/users.spec.ts rename src-wallet/{validatorKey.ts => validatorKeys.ts} (85%) rename src-wallet/{validatorKey.spec.ts => validators.spec.ts} (72%) diff --git a/src-wallet/encryptedKey.spec.ts b/src-wallet/encryptedKey.spec.ts deleted file mode 100644 index 6dd2897b5..000000000 --- a/src-wallet/encryptedKey.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { assert } from "chai"; -import { TestWallets } from "../testutils"; -import { PrivateKey } from "./privateKey"; -import { EncryptedKey, Randomness } from "./encryptedKey"; - -describe("test encrypted key file", () => { - let wallets = new TestWallets(); - let alice = wallets.alice; - let bob = wallets.bob; - let carol = wallets.carol; - let password = wallets.password; - - it("should create and load encrypted files", function() { - this.timeout(10000); - - let alicePrivateKey = PrivateKey.fromString(alice.privateKey); - let bobPrivateKey = PrivateKey.fromString(bob.privateKey); - let carolPrivateKey = PrivateKey.fromString(carol.privateKey); - - console.time("encrypt"); - let aliceKeyFile = new EncryptedKey(alicePrivateKey, password); - let bobKeyFile = new EncryptedKey(bobPrivateKey, password); - let carolKeyFile = new EncryptedKey(carolPrivateKey, password); - console.timeEnd("encrypt"); - - assert.equal(aliceKeyFile.toJSON().bech32, alice.address.bech32()); - assert.equal(bobKeyFile.toJSON().bech32, bob.address.bech32()); - assert.equal(carolKeyFile.toJSON().bech32, carol.address.bech32()); - - console.time("decrypt"); - assert.deepEqual(EncryptedKey.load(aliceKeyFile.toJSON(), password), alicePrivateKey); - assert.deepEqual(EncryptedKey.load(bobKeyFile.toJSON(), password), bobPrivateKey); - assert.deepEqual(EncryptedKey.load(carolKeyFile.toJSON(), password), carolPrivateKey); - console.timeEnd("decrypt"); - - // With provided randomness, in order to reproduce our development wallets - - aliceKeyFile = new EncryptedKey(alicePrivateKey, password, new Randomness({ - id: alice.keyFileObject.id, - iv: Buffer.from(alice.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(alice.keyFileObject.crypto.kdfparams.salt, "hex") - })); - - bobKeyFile = new EncryptedKey(bobPrivateKey, password, new Randomness({ - id: bob.keyFileObject.id, - iv: Buffer.from(bob.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(bob.keyFileObject.crypto.kdfparams.salt, "hex") - })); - - carolKeyFile = new EncryptedKey(carolPrivateKey, password, new Randomness({ - id: carol.keyFileObject.id, - iv: Buffer.from(carol.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(carol.keyFileObject.crypto.kdfparams.salt, "hex") - })); - - assert.deepEqual(aliceKeyFile.toJSON(), alice.keyFileObject); - assert.deepEqual(bobKeyFile.toJSON(), bob.keyFileObject); - assert.deepEqual(carolKeyFile.toJSON(), carol.keyFileObject); - }); -}); diff --git a/src-wallet/mnemonic.spec.ts b/src-wallet/mnemonic.spec.ts deleted file mode 100644 index 2abfb70ce..000000000 --- a/src-wallet/mnemonic.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { assert } from "chai"; -import { Mnemonic } from "./mnemonic"; -import { TestWallets } from "../testutils"; - -describe("test mnemonic", () => { - let wallets = new TestWallets(); - - it("should generate mnemonic", () => { - let mnemonic = Mnemonic.generate(); - let words = mnemonic.getWords(); - assert.lengthOf(words, 24); - }); - - it("should derive keys", () => { - let mnemonic = Mnemonic.fromString(wallets.mnemonic); - - assert.equal(mnemonic.deriveKey(0).toString(), wallets.alice.privateKey); - assert.equal(mnemonic.deriveKey(1).toString(), wallets.bob.privateKey); - assert.equal(mnemonic.deriveKey(2).toString(), wallets.carol.privateKey); - }); -}); diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts index 43b77adc0..ede903f5c 100644 --- a/src-wallet/mnemonic.ts +++ b/src-wallet/mnemonic.ts @@ -1,6 +1,6 @@ import * as errors from "../errors"; import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from "bip39"; -import { PrivateKey } from "./privateKey"; +import { UserPrivateKey } from "./userKeys"; import { derivePath } from "ed25519-hd-key"; const MNEMONIC_STRENGTH = 256; @@ -34,12 +34,12 @@ export class Mnemonic { } // TODO: Question for review: @ccorcoveanu, accountIndex or addressIndex? - deriveKey(index: number = 0, password: string = ""): PrivateKey { + deriveKey(index: number = 0, password: string = ""): UserPrivateKey { let seed = mnemonicToSeedSync(this.text, password); let derivationPath = `${BIP44_DERIVATION_PREFIX}/${index}'`; let derivationResult = derivePath(derivationPath, seed.toString("hex")); let key = derivationResult.key; - return new PrivateKey(key); + return new UserPrivateKey(key); } getWords(): string[] { diff --git a/src-wallet/privateKey.spec.ts b/src-wallet/privateKey.spec.ts deleted file mode 100644 index 7a63560c1..000000000 --- a/src-wallet/privateKey.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as errors from "../errors"; -import { assert } from "chai"; -import { TestWallets } from "../testutils"; -import { PrivateKey } from "./privateKey"; - -describe("test private key", () => { - let wallets = new TestWallets(); - - it("should create", () => { - let keyHex = wallets.alice.privateKey; - let fromBuffer = new PrivateKey(Buffer.from(keyHex, "hex")); - let fromHex = PrivateKey.fromString(keyHex); - - assert.equal(fromBuffer.toString(), keyHex); - assert.equal(fromHex.toString(), keyHex); - }); - - it("should compute public key (and address)", () => { - let privateKey: PrivateKey; - - privateKey = new PrivateKey(Buffer.from(wallets.alice.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().toString(), wallets.alice.address.hex()); - assert.isTrue(privateKey.toPublicKey().toAddress().equals(wallets.alice.address)); - - privateKey = new PrivateKey(Buffer.from(wallets.bob.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().toString(), wallets.bob.address.hex()); - assert.isTrue(privateKey.toPublicKey().toAddress().equals(wallets.bob.address)); - - privateKey = new PrivateKey(Buffer.from(wallets.carol.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().toString(), wallets.carol.address.hex()); - assert.isTrue(privateKey.toPublicKey().toAddress().equals(wallets.carol.address)); - }); - - it("should throw error when invalid input", () => { - assert.throw(() => new PrivateKey(Buffer.alloc(42)), errors.ErrInvariantFailed); - assert.throw(() => PrivateKey.fromString("foobar"), errors.ErrInvariantFailed); - }); -}); diff --git a/src-wallet/privateKey.ts b/src-wallet/privateKey.ts deleted file mode 100644 index 2922153de..000000000 --- a/src-wallet/privateKey.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as tweetnacl from "tweetnacl"; -import { guardLength } from "../utils"; -import { PublicKey } from "./publicKey"; - -export class PrivateKey { - private readonly buffer: Buffer; - - constructor(buffer: Buffer) { - guardLength(buffer, 32); - - this.buffer = buffer; - } - - static fromString(value: string): PrivateKey { - guardLength(value, 64); - - let buffer = Buffer.from(value, "hex"); - return new PrivateKey(buffer); - } - - static fromPEM() { - } - - toPEM() { - } - - toPublicKey(): PublicKey { - // TODO: Question for review: @ccorcoveanu, @AdoAdoAdo, here we use "fromSeed" (instead of fromSecretKey, which wouldn't work on 32-byte private keys). - // TODO: Question for review: is this all right? - let keyPair = tweetnacl.sign.keyPair.fromSeed(this.buffer); - let buffer = Buffer.from(keyPair.publicKey); - return new PublicKey(buffer); - } - - toString(): string { - return this.buffer.toString("hex"); - } - - valueOf(): Buffer { - return this.buffer; - } -} diff --git a/src-wallet/publicKey.ts b/src-wallet/publicKey.ts deleted file mode 100644 index 4f4cf1a12..000000000 --- a/src-wallet/publicKey.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Address } from "../address"; -import { guardLength } from "../utils"; - -export class PublicKey { - private readonly buffer: Buffer; - - constructor(buffer: Buffer) { - guardLength(buffer, 32); - - this.buffer = buffer; - } - - toString(): string { - return this.buffer.toString("hex"); - } - - toAddress(): Address { - return new Address(this.buffer); - } - - valueOf(): Buffer { - return this.buffer; - } -} diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts new file mode 100644 index 000000000..5adcb0467 --- /dev/null +++ b/src-wallet/userKeys.ts @@ -0,0 +1,68 @@ +import * as tweetnacl from "tweetnacl"; +import { Address } from "../address"; +import { guardLength } from "../utils"; + +export const SEED_LENGTH = 32; + +export class UserPrivateKey { + private readonly buffer: Buffer; + + constructor(buffer: Buffer) { + guardLength(buffer, SEED_LENGTH); + + this.buffer = buffer; + } + + static fromString(value: string): UserPrivateKey { + guardLength(value, SEED_LENGTH * 2); + + let buffer = Buffer.from(value, "hex"); + return new UserPrivateKey(buffer); + } + + static fromPEM() { + // todo + } + + toPEM() { + // todo + } + + toPublicKey(): UserPublicKey { + // TODO: Question for review: @ccorcoveanu, @AdoAdoAdo, as opposed to core-js, here we use "fromSeed" (instead of fromSecretKey, which wouldn't work on 32-byte private keys). + // TODO: Question for review: is this all right? + let keyPair = tweetnacl.sign.keyPair.fromSeed(this.buffer); + let buffer = Buffer.from(keyPair.publicKey); + return new UserPublicKey(buffer); + } + + toString(): string { + return this.buffer.toString("hex"); + } + + valueOf(): Buffer { + return this.buffer; + } +} + +export class UserPublicKey { + private readonly buffer: Buffer; + + constructor(buffer: Buffer) { + guardLength(buffer, 32); + + this.buffer = buffer; + } + + toString(): string { + return this.buffer.toString("hex"); + } + + toAddress(): Address { + return new Address(this.buffer); + } + + valueOf(): Buffer { + return this.buffer; + } +} diff --git a/src-wallet/encryptedKey.ts b/src-wallet/userWallet.ts similarity index 82% rename from src-wallet/encryptedKey.ts rename to src-wallet/userWallet.ts index 849f4f027..08fd6e255 100644 --- a/src-wallet/encryptedKey.ts +++ b/src-wallet/userWallet.ts @@ -1,57 +1,24 @@ -import { PrivateKey } from "./privateKey"; import * as errors from "../errors"; import nacl from "tweetnacl"; -import { PublicKey } from "./publicKey"; +import { UserPublicKey, UserPrivateKey } from "./userKeys"; const crypto = require("crypto"); const uuid = require("uuid/v4"); const scryptsy = require("scryptsy"); +// In a future PR, improve versioning infrastructure for key-file objects in erdjs. const Version = 4; const CipherAlgorithm = "aes-128-ctr"; const DigestAlgorithm = "sha256"; const KeyDerivationFunction = "scrypt"; -class ScryptKeyDerivationParams { - /** - * numIterations - */ - n = 4096; - - /** - * memFactor - */ - r = 8; - - /** - * pFactor - */ - p = 1; - - dklen = 32; -} - -export class Randomness { - salt: Buffer; - iv: Buffer; - id: string; - - constructor(init?: Partial) { - this.salt = init?.salt || Buffer.from(nacl.randomBytes(32)); - this.iv = init?.iv || Buffer.from(nacl.randomBytes(16)); - this.id = init?.id || uuid({ random: crypto.randomBytes(16) }); - } -} - -export class EncryptedKey { - private readonly publicKey: PublicKey; +export class UserWallet { + private readonly publicKey: UserPublicKey; private readonly randomness: Randomness; private readonly ciphertext: Buffer; private readonly mac: Buffer; private readonly kdfparams: ScryptKeyDerivationParams; /** - * WIP! This PR is not ready for review yet! - * * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L76 * Notes: adjustements (code refactoring, no change in logic), in terms of: * - typing (since this is the TypeScript version) @@ -59,12 +26,12 @@ export class EncryptedKey { * - references to crypto functions * - references to object members * - * Given a password, it will generate the contents for a file containing the current initialised account's private - * key, passed through a password-based key derivation function (kdf). + * Given a password, generates the contents for a file containing the account's private key, + * passed through a password-based key derivation function (kdf). */ - constructor(privateKey: PrivateKey, password: string, randomness: Randomness = new Randomness()) { + constructor(privateKey: UserPrivateKey, password: string, randomness: Randomness = new Randomness()) { const kdParams = new ScryptKeyDerivationParams(); - const derivedKey = EncryptedKey.generateDerivedKey(Buffer.from(password), randomness.salt, kdParams); + const derivedKey = UserWallet.generateDerivedKey(Buffer.from(password), randomness.salt, kdParams); const derivedKeyFirstHalf = derivedKey.slice(0, 16); const derivedKeySecondHalf = derivedKey.slice(16, 32); const cipher = crypto.createCipheriv(CipherAlgorithm, derivedKeyFirstHalf, randomness.iv); @@ -81,8 +48,6 @@ export class EncryptedKey { } /** - * WIP! This PR is not ready for review yet! - * * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L42 * Notes: adjustements (code refactoring, no change in logic), in terms of: * - typing (since this is the TypeScript version) @@ -90,14 +55,14 @@ export class EncryptedKey { * - references to crypto functions * - references to object members * - * From an encrypted keyfile, given the password, load the private key and the public key. + * From an encrypted keyfile, given the password, loads the private key and the public key. */ - static load(keyFileObject: any, password: string): PrivateKey { + static load(keyFileObject: any, password: string): UserPrivateKey { const kdfparams = keyFileObject.crypto.kdfparams; const salt = Buffer.from(kdfparams.salt, "hex"); const iv = Buffer.from(keyFileObject.crypto.cipherparams.iv, "hex"); const ciphertext = Buffer.from(keyFileObject.crypto.ciphertext, "hex"); - const derivedKey = EncryptedKey.generateDerivedKey(Buffer.from(password), salt, kdfparams); + const derivedKey = UserWallet.generateDerivedKey(Buffer.from(password), salt, kdfparams); const derivedKeyFirstHalf = derivedKey.slice(0, 16); const derivedKeySecondHalf = derivedKey.slice(16, 32); @@ -117,17 +82,13 @@ export class EncryptedKey { } let seed = text.slice(0, 32); - return new PrivateKey(seed); + return new UserPrivateKey(seed); } - // TODO: load() (static), then decrypt(password)... - /** * 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 - * - * TODO: Question for review: @ccorcoveanu, @AdoAdoAdo, is this all right? */ private static generateDerivedKey(password: Buffer, salt: Buffer, kdfparams: ScryptKeyDerivationParams): Buffer { // Question for review: @ccorcoveanu, why not this implementation? @@ -162,3 +123,34 @@ export class EncryptedKey { }; } } + +class ScryptKeyDerivationParams { + /** + * numIterations + */ + n = 4096; + + /** + * memFactor + */ + r = 8; + + /** + * pFactor + */ + p = 1; + + dklen = 32; +} + +export class Randomness { + salt: Buffer; + iv: Buffer; + id: string; + + constructor(init?: Partial) { + this.salt = init?.salt || Buffer.from(nacl.randomBytes(32)); + this.iv = init?.iv || Buffer.from(nacl.randomBytes(16)); + this.id = init?.id || uuid({ random: crypto.randomBytes(16) }); + } +} diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts new file mode 100644 index 000000000..22e1161f2 --- /dev/null +++ b/src-wallet/users.spec.ts @@ -0,0 +1,110 @@ +import * as errors from "../errors"; +import { assert } from "chai"; +import { TestWallets } from "../testutils"; +import { UserPrivateKey } from "./userKeys"; +import { Mnemonic } from "./mnemonic"; +import { UserWallet, Randomness } from "./userWallet"; + +describe("test user wallets", () => { + let wallets = new TestWallets(); + let alice = wallets.alice; + let bob = wallets.bob; + let carol = wallets.carol; + let password = wallets.password; + + it("should generate mnemonic", () => { + let mnemonic = Mnemonic.generate(); + let words = mnemonic.getWords(); + assert.lengthOf(words, 24); + }); + + it("should derive keys", () => { + let mnemonic = Mnemonic.fromString(wallets.mnemonic); + + assert.equal(mnemonic.deriveKey(0).toString(), alice.privateKey); + assert.equal(mnemonic.deriveKey(1).toString(), bob.privateKey); + assert.equal(mnemonic.deriveKey(2).toString(), carol.privateKey); + }); + + it("should create private key", () => { + let keyHex = wallets.alice.privateKey; + let fromBuffer = new UserPrivateKey(Buffer.from(keyHex, "hex")); + let fromHex = UserPrivateKey.fromString(keyHex); + + assert.equal(fromBuffer.toString(), keyHex); + assert.equal(fromHex.toString(), keyHex); + }); + + it("should compute public key (and address)", () => { + let privateKey: UserPrivateKey; + + privateKey = new UserPrivateKey(Buffer.from(alice.privateKey, "hex")); + assert.equal(privateKey.toPublicKey().toString(), alice.address.hex()); + assert.isTrue(privateKey.toPublicKey().toAddress().equals(alice.address)); + + privateKey = new UserPrivateKey(Buffer.from(bob.privateKey, "hex")); + assert.equal(privateKey.toPublicKey().toString(), bob.address.hex()); + assert.isTrue(privateKey.toPublicKey().toAddress().equals(bob.address)); + + privateKey = new UserPrivateKey(Buffer.from(carol.privateKey, "hex")); + assert.equal(privateKey.toPublicKey().toString(), carol.address.hex()); + assert.isTrue(privateKey.toPublicKey().toAddress().equals(carol.address)); + }); + + it("should throw error when invalid input", () => { + assert.throw(() => new UserPrivateKey(Buffer.alloc(42)), errors.ErrInvariantFailed); + assert.throw(() => UserPrivateKey.fromString("foobar"), errors.ErrInvariantFailed); + }); + + it("should handle PEM files", () => { + + }); + + it("should create and load encrypted files", function () { + this.timeout(10000); + + let alicePrivateKey = UserPrivateKey.fromString(alice.privateKey); + let bobPrivateKey = UserPrivateKey.fromString(bob.privateKey); + let carolPrivateKey = UserPrivateKey.fromString(carol.privateKey); + + console.time("encrypt"); + let aliceKeyFile = new UserWallet(alicePrivateKey, password); + let bobKeyFile = new UserWallet(bobPrivateKey, password); + let carolKeyFile = new UserWallet(carolPrivateKey, password); + console.timeEnd("encrypt"); + + assert.equal(aliceKeyFile.toJSON().bech32, alice.address.bech32()); + assert.equal(bobKeyFile.toJSON().bech32, bob.address.bech32()); + assert.equal(carolKeyFile.toJSON().bech32, carol.address.bech32()); + + console.time("decrypt"); + assert.deepEqual(UserWallet.load(aliceKeyFile.toJSON(), password), alicePrivateKey); + assert.deepEqual(UserWallet.load(bobKeyFile.toJSON(), password), bobPrivateKey); + assert.deepEqual(UserWallet.load(carolKeyFile.toJSON(), password), carolPrivateKey); + console.timeEnd("decrypt"); + + // With provided randomness, in order to reproduce our development wallets + + aliceKeyFile = new UserWallet(alicePrivateKey, password, new Randomness({ + id: alice.keyFileObject.id, + iv: Buffer.from(alice.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(alice.keyFileObject.crypto.kdfparams.salt, "hex") + })); + + bobKeyFile = new UserWallet(bobPrivateKey, password, new Randomness({ + id: bob.keyFileObject.id, + iv: Buffer.from(bob.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(bob.keyFileObject.crypto.kdfparams.salt, "hex") + })); + + carolKeyFile = new UserWallet(carolPrivateKey, password, new Randomness({ + id: carol.keyFileObject.id, + iv: Buffer.from(carol.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(carol.keyFileObject.crypto.kdfparams.salt, "hex") + })); + + assert.deepEqual(aliceKeyFile.toJSON(), alice.keyFileObject); + assert.deepEqual(bobKeyFile.toJSON(), bob.keyFileObject); + assert.deepEqual(carolKeyFile.toJSON(), carol.keyFileObject); + }); +}); diff --git a/src-wallet/validatorKey.ts b/src-wallet/validatorKeys.ts similarity index 85% rename from src-wallet/validatorKey.ts rename to src-wallet/validatorKeys.ts index 97b0a9760..91d977b6b 100644 --- a/src-wallet/validatorKey.ts +++ b/src-wallet/validatorKeys.ts @@ -16,7 +16,7 @@ export class BLS { } } -export class ValidatorKey { +export class ValidatorPrivateKey { private readonly secretKey: any; private readonly publicKey: any; @@ -28,6 +28,14 @@ export class ValidatorKey { this.publicKey = this.secretKey.getPublicKey(); } + static fromPEM() { + // todo + } + + toPEM() { + // todo + } + sign(message: Buffer): Buffer { let signatureObject = this.secretKey.sign(message); let signature = Buffer.from(signatureObject.serialize()); @@ -39,3 +47,6 @@ export class ValidatorKey { } } +export class ValidatorPublicKey { + +} diff --git a/src-wallet/validatorKey.spec.ts b/src-wallet/validators.spec.ts similarity index 72% rename from src-wallet/validatorKey.spec.ts rename to src-wallet/validators.spec.ts index ea0ab634a..c03b30b9f 100644 --- a/src-wallet/validatorKey.spec.ts +++ b/src-wallet/validators.spec.ts @@ -1,25 +1,31 @@ import { assert } from "chai"; import { TestWallets } from "../testutils"; -import { BLS, ValidatorKey } from "./validatorKey"; +import { BLS, ValidatorPrivateKey } from "./validatorKeys"; -describe("test validator key", () => { +describe("test validator keys", () => { let wallets = new TestWallets(); - it("should create", async () => { + it("should create private key and sign a message", async () => { await BLS.initIfNecessary(); let privateKey = Buffer.from(Buffer.from("N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw==", "base64").toString(), "hex"); - let key = new ValidatorKey(privateKey); + let key = new ValidatorPrivateKey(privateKey); assert.equal(key.toString(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); let signature = key.sign(Buffer.from("hello")); + // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` assert.equal(signature.toString("hex"), "84fd0a3a9d4f1ea2d4b40c6da67f9b786284a1c3895b7253fec7311597cda3f757862bb0690a92a13ce612c33889fd86"); privateKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); - key = new ValidatorKey(privateKey); + key = new ValidatorPrivateKey(privateKey); assert.equal(key.toString(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); signature = key.sign(Buffer.from("hello")); + // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` assert.equal(signature.toString("hex"), "be2e593ff10899a2ee8e1d5c8094e36c9f48e04b87e129991ff09475808743e07bb41bf6e7bc1463fa554c4b46594b98"); }); + + it("should handle PEM files", () => { + + }); }); From eb22b24e01a1cb90fd2a33978f773e3adfb9cfdc Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 11 Jan 2021 23:37:20 +0200 Subject: [PATCH 010/118] PEM files. Signer components. --- src-wallet/index.ts | 7 +++++ src-wallet/pem.ts | 47 ++++++++++++++++++++++++++++++ src-wallet/userKeys.ts | 22 +++++++++----- src-wallet/userSigner.ts | 55 +++++++++++++++++++++++++++++++++++ src-wallet/userWallet.ts | 2 +- src-wallet/users.spec.ts | 53 ++++++++++++++++++++++++++------- src-wallet/validatorKeys.ts | 32 +++++++++++++++----- src-wallet/validatorSigner.ts | 21 +++++++++++++ 8 files changed, 212 insertions(+), 27 deletions(-) create mode 100644 src-wallet/index.ts create mode 100644 src-wallet/pem.ts create mode 100644 src-wallet/userSigner.ts create mode 100644 src-wallet/validatorSigner.ts diff --git a/src-wallet/index.ts b/src-wallet/index.ts new file mode 100644 index 000000000..64dda6d9e --- /dev/null +++ b/src-wallet/index.ts @@ -0,0 +1,7 @@ +export * from "./mnemonic"; +export * from "./pem"; +export * from "./userWallet"; +export * from "./userKeys"; +export * from "./validatorKeys"; +export * from "./userSigner"; +export * from "./validatorSigner"; diff --git a/src-wallet/pem.ts b/src-wallet/pem.ts new file mode 100644 index 000000000..3a79a5c94 --- /dev/null +++ b/src-wallet/pem.ts @@ -0,0 +1,47 @@ +import { UserPrivateKey } from "./userKeys"; +import { ValidatorPrivateKey } from "./validatorKeys"; + +export function parseUserKey(text: string, index: number = 0): UserPrivateKey { + let keys = parseUserKeys(text); + let key = keys[index]; + return key; +} + +export function parseUserKeys(text: string): UserPrivateKey[] { + let buffers = parse(text); + let keys = buffers.map(buffer => new UserPrivateKey(buffer.slice(0, 32))); + return keys; +} + +export function parseValidatorKey(text: string, index: number = 0): ValidatorPrivateKey { + let keys = parseValidatorKeys(text); + let key = keys[index]; + return key; +} + +export function parseValidatorKeys(text: string): ValidatorPrivateKey[] { + let buffers = parse(text); + let keys = buffers.map(buffer => new ValidatorPrivateKey(buffer)); + return keys; +} + +function parse(text: string): Buffer[] { + let lines = text.split(/\r?\n/); + let buffers: Buffer[] = []; + let linesAccumulator: string[] = []; + + for (const line of lines) { + if (line.startsWith("-----BEGIN")) { + linesAccumulator = []; + } else if (line.startsWith("-----END")) { + let asBase64 = linesAccumulator.join(""); + let asHex = Buffer.from(asBase64, "base64").toString(); + let asBytes = Buffer.from(asHex, "hex"); + buffers.push(asBytes); + } else { + linesAccumulator.push(line); + } + } + + return buffers; +} diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 5adcb0467..5c7dd5815 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -1,6 +1,7 @@ import * as tweetnacl from "tweetnacl"; import { Address } from "../address"; import { guardLength } from "../utils"; +import { parseUserKey } from "./pem"; export const SEED_LENGTH = 32; @@ -20,12 +21,8 @@ export class UserPrivateKey { return new UserPrivateKey(buffer); } - static fromPEM() { - // todo - } - - toPEM() { - // todo + static fromPem(text: string, index: number = 0): UserPrivateKey { + return parseUserKey(text, index); } toPublicKey(): UserPublicKey { @@ -36,7 +33,16 @@ export class UserPrivateKey { return new UserPublicKey(buffer); } - toString(): string { + sign(message: Buffer): Buffer { + let pair = tweetnacl.sign.keyPair.fromSeed(this.buffer); + let signingKey = pair.secretKey; + let signature = tweetnacl.sign(new Uint8Array(message), signingKey); + signature = signature.slice(0, signature.length - message.length); + + return Buffer.from(signature); + } + + hex(): string { return this.buffer.toString("hex"); } @@ -54,7 +60,7 @@ export class UserPublicKey { this.buffer = buffer; } - toString(): string { + hex(): string { return this.buffer.toString("hex"); } diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts new file mode 100644 index 000000000..0e27bfbfa --- /dev/null +++ b/src-wallet/userSigner.ts @@ -0,0 +1,55 @@ +import * as errors from "../errors"; +import { Address } from "../address"; +import { ISignable, ISigner } from "../interface"; +import { Signature } from "../signature"; +import { UserPrivateKey } from "./userKeys"; +import { UserWallet } from "./userWallet"; + +/** + * ed25519 signer + */ +export class UserSigner implements ISigner { + private readonly privateKey: UserPrivateKey; + + constructor(privateKey: UserPrivateKey) { + this.privateKey = privateKey; + } + + static fromWallet(keyFileObject: any, password: string): ISigner { + let privateKey = UserWallet.loadPrivateKey(keyFileObject, password); + return new UserSigner(privateKey); + } + + static fromPem(text: string, index: number = 0) { + let privateKey = UserPrivateKey.fromPem(text, index); + return new UserSigner(privateKey); + } + + /** + * Signs a message. + * @param signable the message to be signed (e.g. a {@link Transaction}). + */ + async sign(signable: ISignable): Promise { + try { + this.trySign(signable); + } catch (err) { + throw new errors.ErrSignerCannotSign(err); + } + } + + private trySign(signable: ISignable) { + let signedBy = this.getAddress(); + let bufferToSign = signable.serializeForSigning(signedBy); + let signatureBuffer = this.privateKey.sign(bufferToSign); + let signature = new Signature(signatureBuffer); + + signable.applySignature(signature, signedBy); + } + + /** + * Gets the address of the signer. + */ + getAddress(): Address { + return this.privateKey.toPublicKey().toAddress(); + } +} diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 08fd6e255..cec6d0167 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -57,7 +57,7 @@ export class UserWallet { * * From an encrypted keyfile, given the password, loads the private key and the public key. */ - static load(keyFileObject: any, password: string): UserPrivateKey { + static loadPrivateKey(keyFileObject: any, password: string): UserPrivateKey { const kdfparams = keyFileObject.crypto.kdfparams; const salt = Buffer.from(kdfparams.salt, "hex"); const iv = Buffer.from(keyFileObject.crypto.cipherparams.iv, "hex"); diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 22e1161f2..24bcbc4bb 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -4,6 +4,13 @@ import { TestWallets } from "../testutils"; import { UserPrivateKey } from "./userKeys"; import { Mnemonic } from "./mnemonic"; import { UserWallet, Randomness } from "./userWallet"; +import { Address } from "../address"; +import { UserSigner } from "./userSigner"; +import { Transaction } from "../transaction"; +import { Nonce } from "../nonce"; +import { Balance } from "../balance"; +import { ChainID, GasLimit, GasPrice, TransactionVersion } from "../networkParams"; +import { TransactionPayload } from "../transactionPayload"; describe("test user wallets", () => { let wallets = new TestWallets(); @@ -21,9 +28,9 @@ describe("test user wallets", () => { it("should derive keys", () => { let mnemonic = Mnemonic.fromString(wallets.mnemonic); - assert.equal(mnemonic.deriveKey(0).toString(), alice.privateKey); - assert.equal(mnemonic.deriveKey(1).toString(), bob.privateKey); - assert.equal(mnemonic.deriveKey(2).toString(), carol.privateKey); + assert.equal(mnemonic.deriveKey(0).hex(), alice.privateKey); + assert.equal(mnemonic.deriveKey(1).hex(), bob.privateKey); + assert.equal(mnemonic.deriveKey(2).hex(), carol.privateKey); }); it("should create private key", () => { @@ -31,23 +38,23 @@ describe("test user wallets", () => { let fromBuffer = new UserPrivateKey(Buffer.from(keyHex, "hex")); let fromHex = UserPrivateKey.fromString(keyHex); - assert.equal(fromBuffer.toString(), keyHex); - assert.equal(fromHex.toString(), keyHex); + assert.equal(fromBuffer.hex(), keyHex); + assert.equal(fromHex.hex(), keyHex); }); it("should compute public key (and address)", () => { let privateKey: UserPrivateKey; privateKey = new UserPrivateKey(Buffer.from(alice.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().toString(), alice.address.hex()); + assert.equal(privateKey.toPublicKey().hex(), alice.address.hex()); assert.isTrue(privateKey.toPublicKey().toAddress().equals(alice.address)); privateKey = new UserPrivateKey(Buffer.from(bob.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().toString(), bob.address.hex()); + assert.equal(privateKey.toPublicKey().hex(), bob.address.hex()); assert.isTrue(privateKey.toPublicKey().toAddress().equals(bob.address)); privateKey = new UserPrivateKey(Buffer.from(carol.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().toString(), carol.address.hex()); + assert.equal(privateKey.toPublicKey().hex(), carol.address.hex()); assert.isTrue(privateKey.toPublicKey().toAddress().equals(carol.address)); }); @@ -78,9 +85,9 @@ describe("test user wallets", () => { assert.equal(carolKeyFile.toJSON().bech32, carol.address.bech32()); console.time("decrypt"); - assert.deepEqual(UserWallet.load(aliceKeyFile.toJSON(), password), alicePrivateKey); - assert.deepEqual(UserWallet.load(bobKeyFile.toJSON(), password), bobPrivateKey); - assert.deepEqual(UserWallet.load(carolKeyFile.toJSON(), password), carolPrivateKey); + assert.deepEqual(UserWallet.loadPrivateKey(aliceKeyFile.toJSON(), password), alicePrivateKey); + assert.deepEqual(UserWallet.loadPrivateKey(bobKeyFile.toJSON(), password), bobPrivateKey); + assert.deepEqual(UserWallet.loadPrivateKey(carolKeyFile.toJSON(), password), carolPrivateKey); console.timeEnd("decrypt"); // With provided randomness, in order to reproduce our development wallets @@ -107,4 +114,28 @@ describe("test user wallets", () => { assert.deepEqual(bobKeyFile.toJSON(), bob.keyFileObject); assert.deepEqual(carolKeyFile.toJSON(), carol.keyFileObject); }); + + it("should sign", async () => { + let signer = new UserSigner(UserPrivateKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); + let sender = new Address("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); + let receiver = new Address("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"); + + + let transaction = new Transaction({ + nonce: new Nonce(0), + value: Balance.Zero(), + receiver: receiver, + gasPrice: new GasPrice(1000000000), + gasLimit: new GasLimit(50000), + data: new TransactionPayload("foo"), + chainID: new ChainID("1"), + version: new TransactionVersion(1) + }); + + let serialized = transaction.serializeForSigning(sender).toString(); + await signer.sign(transaction); + + assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); + assert.equal(transaction.signature.hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); + }); }); diff --git a/src-wallet/validatorKeys.ts b/src-wallet/validatorKeys.ts index 91d977b6b..b3afca8df 100644 --- a/src-wallet/validatorKeys.ts +++ b/src-wallet/validatorKeys.ts @@ -1,4 +1,5 @@ import { guardLength } from "../utils"; +import { parseValidatorKey } from "./pem"; const bls = require('@elrondnetwork/bls-wasm'); @@ -28,12 +29,13 @@ export class ValidatorPrivateKey { this.publicKey = this.secretKey.getPublicKey(); } - static fromPEM() { - // todo + static fromPem(text: string, index: number = 0) { + return parseValidatorKey(text, index); } - toPEM() { - // todo + toPublicKey(): ValidatorPublicKey { + let buffer = Buffer.from(this.publicKey.serialize()); + return new ValidatorPublicKey(buffer); } sign(message: Buffer): Buffer { @@ -42,11 +44,27 @@ export class ValidatorPrivateKey { return signature; } - toString(): string { - return Buffer.from(this.publicKey.serialize()).toString("hex"); + hex(): string { + return this.valueOf().toString("hex"); + } + + valueOf(): Buffer { + return Buffer.from(this.secretKey.serialize()); } } export class ValidatorPublicKey { - + private readonly buffer: Buffer; + + constructor(buffer: Buffer) { + this.buffer = buffer; + } + + hex(): string { + return this.buffer.toString("hex"); + } + + valueOf(): Buffer { + return this.buffer; + } } diff --git a/src-wallet/validatorSigner.ts b/src-wallet/validatorSigner.ts new file mode 100644 index 000000000..811282fcf --- /dev/null +++ b/src-wallet/validatorSigner.ts @@ -0,0 +1,21 @@ +import * as errors from "../errors"; +import { BLS, ValidatorPrivateKey } from "./validatorKeys"; + +/** + * Validator signer (BLS signer) + */ +export class ValidatorSigner { + /** + * Signs a message. + */ + async signUsingPem(pemText: string, pemIndex: number = 0, signable: Buffer): Promise { + await BLS.initIfNecessary(); + + try { + let privateKey = ValidatorPrivateKey.fromPem(pemText, pemIndex); + privateKey.sign(signable); + } catch (err) { + throw new errors.ErrSignerCannotSign(err); + } + } +} From 70eb4a27287b1f372843844e5d035bf12458c260 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 11 Jan 2021 23:42:42 +0200 Subject: [PATCH 011/118] Rename private -> secret. --- src-wallet/mnemonic.ts | 6 ++-- src-wallet/pem.ts | 16 ++++----- src-wallet/userKeys.ts | 8 ++--- src-wallet/userSigner.ts | 20 +++++------ src-wallet/userWallet.ts | 16 ++++----- src-wallet/users.spec.ts | 67 +++++++++++++++++------------------ src-wallet/validatorKeys.ts | 2 +- src-wallet/validatorSigner.ts | 6 ++-- src-wallet/validators.spec.ts | 12 +++---- 9 files changed, 76 insertions(+), 77 deletions(-) diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts index ede903f5c..170a6703e 100644 --- a/src-wallet/mnemonic.ts +++ b/src-wallet/mnemonic.ts @@ -1,6 +1,6 @@ import * as errors from "../errors"; import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from "bip39"; -import { UserPrivateKey } from "./userKeys"; +import { UserSecretKey } from "./userKeys"; import { derivePath } from "ed25519-hd-key"; const MNEMONIC_STRENGTH = 256; @@ -34,12 +34,12 @@ export class Mnemonic { } // TODO: Question for review: @ccorcoveanu, accountIndex or addressIndex? - deriveKey(index: number = 0, password: string = ""): UserPrivateKey { + deriveKey(index: number = 0, password: string = ""): UserSecretKey { let seed = mnemonicToSeedSync(this.text, password); let derivationPath = `${BIP44_DERIVATION_PREFIX}/${index}'`; let derivationResult = derivePath(derivationPath, seed.toString("hex")); let key = derivationResult.key; - return new UserPrivateKey(key); + return new UserSecretKey(key); } getWords(): string[] { diff --git a/src-wallet/pem.ts b/src-wallet/pem.ts index 3a79a5c94..e3569fbd7 100644 --- a/src-wallet/pem.ts +++ b/src-wallet/pem.ts @@ -1,27 +1,27 @@ -import { UserPrivateKey } from "./userKeys"; -import { ValidatorPrivateKey } from "./validatorKeys"; +import { UserSecretKey } from "./userKeys"; +import { ValidatorSecretKey } from "./validatorKeys"; -export function parseUserKey(text: string, index: number = 0): UserPrivateKey { +export function parseUserKey(text: string, index: number = 0): UserSecretKey { let keys = parseUserKeys(text); let key = keys[index]; return key; } -export function parseUserKeys(text: string): UserPrivateKey[] { +export function parseUserKeys(text: string): UserSecretKey[] { let buffers = parse(text); - let keys = buffers.map(buffer => new UserPrivateKey(buffer.slice(0, 32))); + let keys = buffers.map(buffer => new UserSecretKey(buffer.slice(0, 32))); return keys; } -export function parseValidatorKey(text: string, index: number = 0): ValidatorPrivateKey { +export function parseValidatorKey(text: string, index: number = 0): ValidatorSecretKey { let keys = parseValidatorKeys(text); let key = keys[index]; return key; } -export function parseValidatorKeys(text: string): ValidatorPrivateKey[] { +export function parseValidatorKeys(text: string): ValidatorSecretKey[] { let buffers = parse(text); - let keys = buffers.map(buffer => new ValidatorPrivateKey(buffer)); + let keys = buffers.map(buffer => new ValidatorSecretKey(buffer)); return keys; } diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 5c7dd5815..c829d10e4 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -5,7 +5,7 @@ import { parseUserKey } from "./pem"; export const SEED_LENGTH = 32; -export class UserPrivateKey { +export class UserSecretKey { private readonly buffer: Buffer; constructor(buffer: Buffer) { @@ -14,14 +14,14 @@ export class UserPrivateKey { this.buffer = buffer; } - static fromString(value: string): UserPrivateKey { + static fromString(value: string): UserSecretKey { guardLength(value, SEED_LENGTH * 2); let buffer = Buffer.from(value, "hex"); - return new UserPrivateKey(buffer); + return new UserSecretKey(buffer); } - static fromPem(text: string, index: number = 0): UserPrivateKey { + static fromPem(text: string, index: number = 0): UserSecretKey { return parseUserKey(text, index); } diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 0e27bfbfa..95b9ad139 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -2,27 +2,27 @@ import * as errors from "../errors"; import { Address } from "../address"; import { ISignable, ISigner } from "../interface"; import { Signature } from "../signature"; -import { UserPrivateKey } from "./userKeys"; +import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; /** * ed25519 signer */ export class UserSigner implements ISigner { - private readonly privateKey: UserPrivateKey; + private readonly secretKey: UserSecretKey; - constructor(privateKey: UserPrivateKey) { - this.privateKey = privateKey; + constructor(secretKey: UserSecretKey) { + this.secretKey = secretKey; } static fromWallet(keyFileObject: any, password: string): ISigner { - let privateKey = UserWallet.loadPrivateKey(keyFileObject, password); - return new UserSigner(privateKey); + let secretKey = UserWallet.decryptSecretKey(keyFileObject, password); + return new UserSigner(secretKey); } static fromPem(text: string, index: number = 0) { - let privateKey = UserPrivateKey.fromPem(text, index); - return new UserSigner(privateKey); + let secretKey = UserSecretKey.fromPem(text, index); + return new UserSigner(secretKey); } /** @@ -40,7 +40,7 @@ export class UserSigner implements ISigner { private trySign(signable: ISignable) { let signedBy = this.getAddress(); let bufferToSign = signable.serializeForSigning(signedBy); - let signatureBuffer = this.privateKey.sign(bufferToSign); + let signatureBuffer = this.secretKey.sign(bufferToSign); let signature = new Signature(signatureBuffer); signable.applySignature(signature, signedBy); @@ -50,6 +50,6 @@ export class UserSigner implements ISigner { * Gets the address of the signer. */ getAddress(): Address { - return this.privateKey.toPublicKey().toAddress(); + return this.secretKey.toPublicKey().toAddress(); } } diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index cec6d0167..87706baa3 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -1,6 +1,6 @@ import * as errors from "../errors"; import nacl from "tweetnacl"; -import { UserPublicKey, UserPrivateKey } from "./userKeys"; +import { UserPublicKey, UserSecretKey } from "./userKeys"; const crypto = require("crypto"); const uuid = require("uuid/v4"); const scryptsy = require("scryptsy"); @@ -26,21 +26,21 @@ export class UserWallet { * - references to crypto functions * - references to object members * - * Given a password, generates the contents for a file containing the account's private key, + * Given a password, generates the contents for a file containing the account's secret key, * passed through a password-based key derivation function (kdf). */ - constructor(privateKey: UserPrivateKey, password: string, randomness: Randomness = new Randomness()) { + constructor(secretKey: UserSecretKey, password: string, randomness: Randomness = new Randomness()) { const kdParams = new ScryptKeyDerivationParams(); const derivedKey = UserWallet.generateDerivedKey(Buffer.from(password), randomness.salt, kdParams); const derivedKeyFirstHalf = derivedKey.slice(0, 16); const derivedKeySecondHalf = derivedKey.slice(16, 32); const cipher = crypto.createCipheriv(CipherAlgorithm, derivedKeyFirstHalf, randomness.iv); - const text = Buffer.concat([privateKey.valueOf(), privateKey.toPublicKey().valueOf()]); + const text = Buffer.concat([secretKey.valueOf(), secretKey.toPublicKey().valueOf()]); const ciphertext = Buffer.concat([cipher.update(text), cipher.final()]); const mac = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest(); - this.publicKey = privateKey.toPublicKey(); + this.publicKey = secretKey.toPublicKey(); this.randomness = randomness; this.ciphertext = ciphertext; this.mac = mac; @@ -55,9 +55,9 @@ export class UserWallet { * - references to crypto functions * - references to object members * - * From an encrypted keyfile, given the password, loads the private key and the public key. + * From an encrypted keyfile, given the password, loads the secret key and the public key. */ - static loadPrivateKey(keyFileObject: any, password: string): UserPrivateKey { + static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { const kdfparams = keyFileObject.crypto.kdfparams; const salt = Buffer.from(kdfparams.salt, "hex"); const iv = Buffer.from(keyFileObject.crypto.cipherparams.iv, "hex"); @@ -82,7 +82,7 @@ export class UserWallet { } let seed = text.slice(0, 32); - return new UserPrivateKey(seed); + return new UserSecretKey(seed); } /** diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 24bcbc4bb..8801c3b38 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -1,7 +1,7 @@ import * as errors from "../errors"; import { assert } from "chai"; import { TestWallets } from "../testutils"; -import { UserPrivateKey } from "./userKeys"; +import { UserSecretKey } from "./userKeys"; import { Mnemonic } from "./mnemonic"; import { UserWallet, Randomness } from "./userWallet"; import { Address } from "../address"; @@ -28,39 +28,39 @@ describe("test user wallets", () => { it("should derive keys", () => { let mnemonic = Mnemonic.fromString(wallets.mnemonic); - assert.equal(mnemonic.deriveKey(0).hex(), alice.privateKey); - assert.equal(mnemonic.deriveKey(1).hex(), bob.privateKey); - assert.equal(mnemonic.deriveKey(2).hex(), carol.privateKey); + assert.equal(mnemonic.deriveKey(0).hex(), alice.secretKeyHex); + assert.equal(mnemonic.deriveKey(1).hex(), bob.secretKeyHex); + assert.equal(mnemonic.deriveKey(2).hex(), carol.secretKeyHex); }); - it("should create private key", () => { - let keyHex = wallets.alice.privateKey; - let fromBuffer = new UserPrivateKey(Buffer.from(keyHex, "hex")); - let fromHex = UserPrivateKey.fromString(keyHex); + it("should create secret key", () => { + let keyHex = wallets.alice.secretKeyHex; + let fromBuffer = new UserSecretKey(Buffer.from(keyHex, "hex")); + let fromHex = UserSecretKey.fromString(keyHex); assert.equal(fromBuffer.hex(), keyHex); assert.equal(fromHex.hex(), keyHex); }); it("should compute public key (and address)", () => { - let privateKey: UserPrivateKey; + let secretKey: UserSecretKey; - privateKey = new UserPrivateKey(Buffer.from(alice.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().hex(), alice.address.hex()); - assert.isTrue(privateKey.toPublicKey().toAddress().equals(alice.address)); + secretKey = new UserSecretKey(Buffer.from(alice.secretKeyHex, "hex")); + assert.equal(secretKey.toPublicKey().hex(), alice.address.hex()); + assert.isTrue(secretKey.toPublicKey().toAddress().equals(alice.address)); - privateKey = new UserPrivateKey(Buffer.from(bob.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().hex(), bob.address.hex()); - assert.isTrue(privateKey.toPublicKey().toAddress().equals(bob.address)); + secretKey = new UserSecretKey(Buffer.from(bob.secretKeyHex, "hex")); + assert.equal(secretKey.toPublicKey().hex(), bob.address.hex()); + assert.isTrue(secretKey.toPublicKey().toAddress().equals(bob.address)); - privateKey = new UserPrivateKey(Buffer.from(carol.privateKey, "hex")); - assert.equal(privateKey.toPublicKey().hex(), carol.address.hex()); - assert.isTrue(privateKey.toPublicKey().toAddress().equals(carol.address)); + secretKey = new UserSecretKey(Buffer.from(carol.secretKeyHex, "hex")); + assert.equal(secretKey.toPublicKey().hex(), carol.address.hex()); + assert.isTrue(secretKey.toPublicKey().toAddress().equals(carol.address)); }); it("should throw error when invalid input", () => { - assert.throw(() => new UserPrivateKey(Buffer.alloc(42)), errors.ErrInvariantFailed); - assert.throw(() => UserPrivateKey.fromString("foobar"), errors.ErrInvariantFailed); + assert.throw(() => new UserSecretKey(Buffer.alloc(42)), errors.ErrInvariantFailed); + assert.throw(() => UserSecretKey.fromString("foobar"), errors.ErrInvariantFailed); }); it("should handle PEM files", () => { @@ -70,14 +70,14 @@ describe("test user wallets", () => { it("should create and load encrypted files", function () { this.timeout(10000); - let alicePrivateKey = UserPrivateKey.fromString(alice.privateKey); - let bobPrivateKey = UserPrivateKey.fromString(bob.privateKey); - let carolPrivateKey = UserPrivateKey.fromString(carol.privateKey); + let aliceSecretKey = UserSecretKey.fromString(alice.secretKeyHex); + let bobSecretKey = UserSecretKey.fromString(bob.secretKeyHex); + let carolSecretKey = UserSecretKey.fromString(carol.secretKeyHex); console.time("encrypt"); - let aliceKeyFile = new UserWallet(alicePrivateKey, password); - let bobKeyFile = new UserWallet(bobPrivateKey, password); - let carolKeyFile = new UserWallet(carolPrivateKey, password); + let aliceKeyFile = new UserWallet(aliceSecretKey, password); + let bobKeyFile = new UserWallet(bobSecretKey, password); + let carolKeyFile = new UserWallet(carolSecretKey, password); console.timeEnd("encrypt"); assert.equal(aliceKeyFile.toJSON().bech32, alice.address.bech32()); @@ -85,26 +85,26 @@ describe("test user wallets", () => { assert.equal(carolKeyFile.toJSON().bech32, carol.address.bech32()); console.time("decrypt"); - assert.deepEqual(UserWallet.loadPrivateKey(aliceKeyFile.toJSON(), password), alicePrivateKey); - assert.deepEqual(UserWallet.loadPrivateKey(bobKeyFile.toJSON(), password), bobPrivateKey); - assert.deepEqual(UserWallet.loadPrivateKey(carolKeyFile.toJSON(), password), carolPrivateKey); + assert.deepEqual(UserWallet.decryptSecretKey(aliceKeyFile.toJSON(), password), aliceSecretKey); + assert.deepEqual(UserWallet.decryptSecretKey(bobKeyFile.toJSON(), password), bobSecretKey); + assert.deepEqual(UserWallet.decryptSecretKey(carolKeyFile.toJSON(), password), carolSecretKey); console.timeEnd("decrypt"); // With provided randomness, in order to reproduce our development wallets - aliceKeyFile = new UserWallet(alicePrivateKey, password, new Randomness({ + aliceKeyFile = new UserWallet(aliceSecretKey, password, new Randomness({ id: alice.keyFileObject.id, iv: Buffer.from(alice.keyFileObject.crypto.cipherparams.iv, "hex"), salt: Buffer.from(alice.keyFileObject.crypto.kdfparams.salt, "hex") })); - bobKeyFile = new UserWallet(bobPrivateKey, password, new Randomness({ + bobKeyFile = new UserWallet(bobSecretKey, password, new Randomness({ id: bob.keyFileObject.id, iv: Buffer.from(bob.keyFileObject.crypto.cipherparams.iv, "hex"), salt: Buffer.from(bob.keyFileObject.crypto.kdfparams.salt, "hex") })); - carolKeyFile = new UserWallet(carolPrivateKey, password, new Randomness({ + carolKeyFile = new UserWallet(carolSecretKey, password, new Randomness({ id: carol.keyFileObject.id, iv: Buffer.from(carol.keyFileObject.crypto.cipherparams.iv, "hex"), salt: Buffer.from(carol.keyFileObject.crypto.kdfparams.salt, "hex") @@ -116,11 +116,10 @@ describe("test user wallets", () => { }); it("should sign", async () => { - let signer = new UserSigner(UserPrivateKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); + let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let sender = new Address("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); let receiver = new Address("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"); - let transaction = new Transaction({ nonce: new Nonce(0), value: Balance.Zero(), diff --git a/src-wallet/validatorKeys.ts b/src-wallet/validatorKeys.ts index b3afca8df..7651029f6 100644 --- a/src-wallet/validatorKeys.ts +++ b/src-wallet/validatorKeys.ts @@ -17,7 +17,7 @@ export class BLS { } } -export class ValidatorPrivateKey { +export class ValidatorSecretKey { private readonly secretKey: any; private readonly publicKey: any; diff --git a/src-wallet/validatorSigner.ts b/src-wallet/validatorSigner.ts index 811282fcf..887587f57 100644 --- a/src-wallet/validatorSigner.ts +++ b/src-wallet/validatorSigner.ts @@ -1,5 +1,5 @@ import * as errors from "../errors"; -import { BLS, ValidatorPrivateKey } from "./validatorKeys"; +import { BLS, ValidatorSecretKey } from "./validatorKeys"; /** * Validator signer (BLS signer) @@ -12,8 +12,8 @@ export class ValidatorSigner { await BLS.initIfNecessary(); try { - let privateKey = ValidatorPrivateKey.fromPem(pemText, pemIndex); - privateKey.sign(signable); + let secretKey = ValidatorSecretKey.fromPem(pemText, pemIndex); + secretKey.sign(signable); } catch (err) { throw new errors.ErrSignerCannotSign(err); } diff --git a/src-wallet/validators.spec.ts b/src-wallet/validators.spec.ts index c03b30b9f..82e00df27 100644 --- a/src-wallet/validators.spec.ts +++ b/src-wallet/validators.spec.ts @@ -1,23 +1,23 @@ import { assert } from "chai"; import { TestWallets } from "../testutils"; -import { BLS, ValidatorPrivateKey } from "./validatorKeys"; +import { BLS, ValidatorSecretKey } from "./validatorKeys"; describe("test validator keys", () => { let wallets = new TestWallets(); - it("should create private key and sign a message", async () => { + it("should create secret key and sign a message", async () => { await BLS.initIfNecessary(); - let privateKey = Buffer.from(Buffer.from("N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw==", "base64").toString(), "hex"); - let key = new ValidatorPrivateKey(privateKey); + let secretKey = Buffer.from(Buffer.from("N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw==", "base64").toString(), "hex"); + let key = new ValidatorSecretKey(secretKey); assert.equal(key.toString(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); let signature = key.sign(Buffer.from("hello")); // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` assert.equal(signature.toString("hex"), "84fd0a3a9d4f1ea2d4b40c6da67f9b786284a1c3895b7253fec7311597cda3f757862bb0690a92a13ce612c33889fd86"); - privateKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); - key = new ValidatorPrivateKey(privateKey); + secretKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); + key = new ValidatorSecretKey(secretKey); assert.equal(key.toString(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); signature = key.sign(Buffer.from("hello")); From b48994b5410d641215fd19dae5c412d9e08e3941 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 12 Jan 2021 00:22:43 +0200 Subject: [PATCH 012/118] Add tests, fix tests. --- src-wallet/userWallet.ts | 6 +++--- src-wallet/users.spec.ts | 24 ++++++++++++++++++++++-- src-wallet/validators.spec.ts | 14 ++++++++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 87706baa3..cb3e8eb9d 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -2,7 +2,7 @@ import * as errors from "../errors"; import nacl from "tweetnacl"; import { UserPublicKey, UserSecretKey } from "./userKeys"; const crypto = require("crypto"); -const uuid = require("uuid/v4"); +import { v4 as uuidv4 } from "uuid"; const scryptsy = require("scryptsy"); // In a future PR, improve versioning infrastructure for key-file objects in erdjs. @@ -104,7 +104,7 @@ export class UserWallet { return { version: Version, id: this.randomness.id, - address: this.publicKey.toString(), + address: this.publicKey.hex(), bech32: this.publicKey.toAddress().toString(), crypto: { ciphertext: this.ciphertext.toString("hex"), @@ -151,6 +151,6 @@ export class Randomness { constructor(init?: Partial) { this.salt = init?.salt || Buffer.from(nacl.randomBytes(32)); this.iv = init?.iv || Buffer.from(nacl.randomBytes(16)); - this.id = init?.id || uuid({ random: crypto.randomBytes(16) }); + this.id = init?.id || uuidv4({ random: crypto.randomBytes(16) }); } } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 8801c3b38..7432b99e0 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -64,7 +64,9 @@ describe("test user wallets", () => { }); it("should handle PEM files", () => { - + assert.equal(UserSecretKey.fromPem(alice.pemFileText).hex(), alice.secretKeyHex); + assert.equal(UserSecretKey.fromPem(bob.pemFileText).hex(), bob.secretKeyHex); + assert.equal(UserSecretKey.fromPem(carol.pemFileText).hex(), carol.secretKeyHex); }); it("should create and load encrypted files", function () { @@ -115,11 +117,12 @@ describe("test user wallets", () => { assert.deepEqual(carolKeyFile.toJSON(), carol.keyFileObject); }); - it("should sign", async () => { + it("should sign transactions", async () => { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let sender = new Address("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); let receiver = new Address("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"); + // With data field let transaction = new Transaction({ nonce: new Nonce(0), value: Balance.Zero(), @@ -136,5 +139,22 @@ describe("test user wallets", () => { assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); assert.equal(transaction.signature.hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); + + // Without data field + transaction = new Transaction({ + nonce: new Nonce(8), + value: Balance.fromString("10000000000000000000"), + receiver: receiver, + gasPrice: new GasPrice(1000000000), + gasLimit: new GasLimit(50000), + chainID: new ChainID("1"), + version: new TransactionVersion(1) + }); + + serialized = transaction.serializeForSigning(sender).toString(); + await signer.sign(transaction); + + assert.equal(serialized, `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); + assert.equal(transaction.signature.hex(), "4a6d8186eae110894e7417af82c9bf9592696c0600faf110972e0e5310d8485efc656b867a2336acec2b4c1e5f76c9cc70ba1803c6a46455ed7f1e2989a90105"); }); }); diff --git a/src-wallet/validators.spec.ts b/src-wallet/validators.spec.ts index 82e00df27..df771c7bb 100644 --- a/src-wallet/validators.spec.ts +++ b/src-wallet/validators.spec.ts @@ -10,7 +10,7 @@ describe("test validator keys", () => { let secretKey = Buffer.from(Buffer.from("N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw==", "base64").toString(), "hex"); let key = new ValidatorSecretKey(secretKey); - assert.equal(key.toString(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); + assert.equal(key.toPublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); let signature = key.sign(Buffer.from("hello")); // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` @@ -18,14 +18,20 @@ describe("test validator keys", () => { secretKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); key = new ValidatorSecretKey(secretKey); - assert.equal(key.toString(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); + assert.equal(key.toPublicKey().hex(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); signature = key.sign(Buffer.from("hello")); // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` assert.equal(signature.toString("hex"), "be2e593ff10899a2ee8e1d5c8094e36c9f48e04b87e129991ff09475808743e07bb41bf6e7bc1463fa554c4b46594b98"); }); - it("should handle PEM files", () => { - + it("should handle PEM files", async () => { + await BLS.initIfNecessary(); + + let text = `-----BEGIN foobar +N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw== +-----END foobar`; + assert.equal(ValidatorSecretKey.fromPem(text).hex(), "7cff99bd671502db7d15bc8abc0c9a804fb925406fbdd50f1e4c17a4cd774247"); + assert.equal(ValidatorSecretKey.fromPem(text).toPublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); }); }); From 38c145a8b97a33638ede35da9fcc61d2a33e2e74 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Tue, 12 Jan 2021 10:33:22 +0200 Subject: [PATCH 013/118] Fix after merge. --- src-wallet/users.spec.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 7432b99e0..7e1c2601f 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -130,15 +130,14 @@ describe("test user wallets", () => { gasPrice: new GasPrice(1000000000), gasLimit: new GasLimit(50000), data: new TransactionPayload("foo"), - chainID: new ChainID("1"), - version: new TransactionVersion(1) + chainID: new ChainID("1") }); let serialized = transaction.serializeForSigning(sender).toString(); await signer.sign(transaction); assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); - assert.equal(transaction.signature.hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); + assert.equal(transaction.getSignature().hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); // Without data field transaction = new Transaction({ @@ -147,14 +146,13 @@ describe("test user wallets", () => { receiver: receiver, gasPrice: new GasPrice(1000000000), gasLimit: new GasLimit(50000), - chainID: new ChainID("1"), - version: new TransactionVersion(1) + chainID: new ChainID("1") }); serialized = transaction.serializeForSigning(sender).toString(); await signer.sign(transaction); assert.equal(serialized, `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); - assert.equal(transaction.signature.hex(), "4a6d8186eae110894e7417af82c9bf9592696c0600faf110972e0e5310d8485efc656b867a2336acec2b4c1e5f76c9cc70ba1803c6a46455ed7f1e2989a90105"); + assert.equal(transaction.getSignature().hex(), "4a6d8186eae110894e7417af82c9bf9592696c0600faf110972e0e5310d8485efc656b867a2336acec2b4c1e5f76c9cc70ba1803c6a46455ed7f1e2989a90105"); }); }); From 676b984e1fefdc7317b66d30927e23cd5bcabf74 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Fri, 15 Jan 2021 13:37:07 +0200 Subject: [PATCH 014/118] Fix after review, part 1. --- src-wallet/mnemonic.ts | 5 ++--- src-wallet/pem.ts | 12 ++++-------- src-wallet/userKeys.ts | 10 +++++----- src-wallet/userSigner.ts | 2 +- src-wallet/userWallet.ts | 4 ++-- src-wallet/users.spec.ts | 12 ++++++------ src-wallet/validatorKeys.ts | 9 +++++++-- src-wallet/validators.spec.ts | 6 +++--- 8 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts index 170a6703e..69c298d66 100644 --- a/src-wallet/mnemonic.ts +++ b/src-wallet/mnemonic.ts @@ -33,10 +33,9 @@ export class Mnemonic { } } - // TODO: Question for review: @ccorcoveanu, accountIndex or addressIndex? - deriveKey(index: number = 0, password: string = ""): UserSecretKey { + deriveKey(addressIndex: number = 0, password: string = ""): UserSecretKey { let seed = mnemonicToSeedSync(this.text, password); - let derivationPath = `${BIP44_DERIVATION_PREFIX}/${index}'`; + let derivationPath = `${BIP44_DERIVATION_PREFIX}/${addressIndex}'`; let derivationResult = derivePath(derivationPath, seed.toString("hex")); let key = derivationResult.key; return new UserSecretKey(key); diff --git a/src-wallet/pem.ts b/src-wallet/pem.ts index e3569fbd7..5cb189143 100644 --- a/src-wallet/pem.ts +++ b/src-wallet/pem.ts @@ -3,26 +3,22 @@ import { ValidatorSecretKey } from "./validatorKeys"; export function parseUserKey(text: string, index: number = 0): UserSecretKey { let keys = parseUserKeys(text); - let key = keys[index]; - return key; + return keys[index]; } export function parseUserKeys(text: string): UserSecretKey[] { let buffers = parse(text); - let keys = buffers.map(buffer => new UserSecretKey(buffer.slice(0, 32))); - return keys; + return buffers.map(buffer => new UserSecretKey(buffer.slice(0, 32))); } export function parseValidatorKey(text: string, index: number = 0): ValidatorSecretKey { let keys = parseValidatorKeys(text); - let key = keys[index]; - return key; + return keys[index]; } export function parseValidatorKeys(text: string): ValidatorSecretKey[] { let buffers = parse(text); - let keys = buffers.map(buffer => new ValidatorSecretKey(buffer)); - return keys; + return buffers.map(buffer => new ValidatorSecretKey(buffer)); } function parse(text: string): Buffer[] { diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index c829d10e4..0294818ab 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -3,7 +3,8 @@ import { Address } from "../address"; import { guardLength } from "../utils"; import { parseUserKey } from "./pem"; -export const SEED_LENGTH = 32; +const SEED_LENGTH = 32; +const PUBKEY_LENGTH = 32; export class UserSecretKey { private readonly buffer: Buffer; @@ -25,9 +26,7 @@ export class UserSecretKey { return parseUserKey(text, index); } - toPublicKey(): UserPublicKey { - // TODO: Question for review: @ccorcoveanu, @AdoAdoAdo, as opposed to core-js, here we use "fromSeed" (instead of fromSecretKey, which wouldn't work on 32-byte private keys). - // TODO: Question for review: is this all right? + generatePublicKey(): UserPublicKey { let keyPair = tweetnacl.sign.keyPair.fromSeed(this.buffer); let buffer = Buffer.from(keyPair.publicKey); return new UserPublicKey(buffer); @@ -37,6 +36,7 @@ export class UserSecretKey { let pair = tweetnacl.sign.keyPair.fromSeed(this.buffer); let signingKey = pair.secretKey; let signature = tweetnacl.sign(new Uint8Array(message), signingKey); + // "tweetnacl.sign()" returns the concatenated [signature, message], therfore we remove the appended message: signature = signature.slice(0, signature.length - message.length); return Buffer.from(signature); @@ -55,7 +55,7 @@ export class UserPublicKey { private readonly buffer: Buffer; constructor(buffer: Buffer) { - guardLength(buffer, 32); + guardLength(buffer, PUBKEY_LENGTH); this.buffer = buffer; } diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 95b9ad139..be0b30e84 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -50,6 +50,6 @@ export class UserSigner implements ISigner { * Gets the address of the signer. */ getAddress(): Address { - return this.secretKey.toPublicKey().toAddress(); + return this.secretKey.generatePublicKey().toAddress(); } } diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index cb3e8eb9d..a2fb39dda 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -36,11 +36,11 @@ export class UserWallet { const derivedKeySecondHalf = derivedKey.slice(16, 32); const cipher = crypto.createCipheriv(CipherAlgorithm, derivedKeyFirstHalf, randomness.iv); - const text = Buffer.concat([secretKey.valueOf(), secretKey.toPublicKey().valueOf()]); + const text = Buffer.concat([secretKey.valueOf(), secretKey.generatePublicKey().valueOf()]); const ciphertext = Buffer.concat([cipher.update(text), cipher.final()]); const mac = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest(); - this.publicKey = secretKey.toPublicKey(); + this.publicKey = secretKey.generatePublicKey(); this.randomness = randomness; this.ciphertext = ciphertext; this.mac = mac; diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 7432b99e0..510aa39a4 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -46,16 +46,16 @@ describe("test user wallets", () => { let secretKey: UserSecretKey; secretKey = new UserSecretKey(Buffer.from(alice.secretKeyHex, "hex")); - assert.equal(secretKey.toPublicKey().hex(), alice.address.hex()); - assert.isTrue(secretKey.toPublicKey().toAddress().equals(alice.address)); + assert.equal(secretKey.generatePublicKey().hex(), alice.address.hex()); + assert.isTrue(secretKey.generatePublicKey().toAddress().equals(alice.address)); secretKey = new UserSecretKey(Buffer.from(bob.secretKeyHex, "hex")); - assert.equal(secretKey.toPublicKey().hex(), bob.address.hex()); - assert.isTrue(secretKey.toPublicKey().toAddress().equals(bob.address)); + assert.equal(secretKey.generatePublicKey().hex(), bob.address.hex()); + assert.isTrue(secretKey.generatePublicKey().toAddress().equals(bob.address)); secretKey = new UserSecretKey(Buffer.from(carol.secretKeyHex, "hex")); - assert.equal(secretKey.toPublicKey().hex(), carol.address.hex()); - assert.isTrue(secretKey.toPublicKey().toAddress().equals(carol.address)); + assert.equal(secretKey.generatePublicKey().hex(), carol.address.hex()); + assert.isTrue(secretKey.generatePublicKey().toAddress().equals(carol.address)); }); it("should throw error when invalid input", () => { diff --git a/src-wallet/validatorKeys.ts b/src-wallet/validatorKeys.ts index 7651029f6..de085ff46 100644 --- a/src-wallet/validatorKeys.ts +++ b/src-wallet/validatorKeys.ts @@ -3,6 +3,9 @@ import { parseValidatorKey } from "./pem"; const bls = require('@elrondnetwork/bls-wasm'); +const SECRETKEY_LENGTH = 32; +const PUBKEY_LENGTH = 96; + export class BLS { private static isInitialized: boolean = false; @@ -22,7 +25,7 @@ export class ValidatorSecretKey { private readonly publicKey: any; constructor(buffer: Buffer) { - guardLength(buffer, 32); + guardLength(buffer, SECRETKEY_LENGTH); this.secretKey = new bls.SecretKey(); this.secretKey.setLittleEndian(Uint8Array.from(buffer)); @@ -33,7 +36,7 @@ export class ValidatorSecretKey { return parseValidatorKey(text, index); } - toPublicKey(): ValidatorPublicKey { + generatePublicKey(): ValidatorPublicKey { let buffer = Buffer.from(this.publicKey.serialize()); return new ValidatorPublicKey(buffer); } @@ -57,6 +60,8 @@ export class ValidatorPublicKey { private readonly buffer: Buffer; constructor(buffer: Buffer) { + guardLength(buffer, PUBKEY_LENGTH); + this.buffer = buffer; } diff --git a/src-wallet/validators.spec.ts b/src-wallet/validators.spec.ts index df771c7bb..e737e2f5f 100644 --- a/src-wallet/validators.spec.ts +++ b/src-wallet/validators.spec.ts @@ -10,7 +10,7 @@ describe("test validator keys", () => { let secretKey = Buffer.from(Buffer.from("N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw==", "base64").toString(), "hex"); let key = new ValidatorSecretKey(secretKey); - assert.equal(key.toPublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); + assert.equal(key.generatePublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); let signature = key.sign(Buffer.from("hello")); // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` @@ -18,7 +18,7 @@ describe("test validator keys", () => { secretKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); key = new ValidatorSecretKey(secretKey); - assert.equal(key.toPublicKey().hex(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); + assert.equal(key.generatePublicKey().hex(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); signature = key.sign(Buffer.from("hello")); // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` @@ -32,6 +32,6 @@ describe("test validator keys", () => { N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw== -----END foobar`; assert.equal(ValidatorSecretKey.fromPem(text).hex(), "7cff99bd671502db7d15bc8abc0c9a804fb925406fbdd50f1e4c17a4cd774247"); - assert.equal(ValidatorSecretKey.fromPem(text).toPublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); + assert.equal(ValidatorSecretKey.fromPem(text).generatePublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); }); }); From 674880377a29db3b6517f8b3a634ee0a52277018 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Fri, 15 Jan 2021 14:13:01 +0200 Subject: [PATCH 015/118] Fix after review, part 2. --- src-wallet/userWallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index a2fb39dda..423cfaea5 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -3,7 +3,7 @@ import nacl from "tweetnacl"; import { UserPublicKey, UserSecretKey } from "./userKeys"; const crypto = require("crypto"); import { v4 as uuidv4 } from "uuid"; -const scryptsy = require("scryptsy"); +import scryptsy from "scryptsy"; // In a future PR, improve versioning infrastructure for key-file objects in erdjs. const Version = 4; From 66760e420a50353c9e75d4f18bb0ef6b0b8a910f Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 10 Feb 2021 11:18:20 +0200 Subject: [PATCH 016/118] Add test for signing using PEM file. --- src-wallet/users.spec.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 510aa39a4..d0b6a159f 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -157,4 +157,22 @@ describe("test user wallets", () => { assert.equal(serialized, `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); assert.equal(transaction.signature.hex(), "4a6d8186eae110894e7417af82c9bf9592696c0600faf110972e0e5310d8485efc656b867a2336acec2b4c1e5f76c9cc70ba1803c6a46455ed7f1e2989a90105"); }); + + it("should sign transactions using PEM files", async () => { + let signer = UserSigner.fromPem(alice.pemFileText); + + let transaction = new Transaction({ + nonce: new Nonce(0), + value: Balance.Zero(), + receiver: new Address("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"), + gasPrice: new GasPrice(1000000000), + gasLimit: new GasLimit(50000), + data: new TransactionPayload("foo"), + chainID: new ChainID("1"), + version: new TransactionVersion(1) + }); + + await signer.sign(transaction); + assert.equal(transaction.signature.hex(), "c0bd2b3b33a07b9cc5ee7435228acb0936b3829c7008aacabceea35163e555e19a34def2c03a895cf36b0bcec30a7e11215c11efc0da29294a11234eb2b3b906"); + }); }); From a6478f8f260ec9a5bcd14907c105bc2c5f0368df Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 10 Feb 2021 13:49:33 +0200 Subject: [PATCH 017/118] Extra checks, error handling and tests for PEM parsing etc. --- src-wallet/pem.spec.ts | 93 +++++++++++++++++++++++++++++++++++++ src-wallet/pem.ts | 27 ++++++++--- src-wallet/userKeys.ts | 10 ++-- src-wallet/validatorKeys.ts | 16 +++++-- 4 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 src-wallet/pem.spec.ts diff --git a/src-wallet/pem.spec.ts b/src-wallet/pem.spec.ts new file mode 100644 index 000000000..591e379b8 --- /dev/null +++ b/src-wallet/pem.spec.ts @@ -0,0 +1,93 @@ +import * as errors from "../errors"; +import { assert } from "chai"; +import { TestWallets } from "../testutils"; +import { parse, parseUserKey, parseValidatorKey } from "./pem"; +import { BLS } from "."; +import { Buffer } from "buffer"; + +describe("test PEMs", () => { + let wallets = new TestWallets(); + let alice = wallets.alice; + let bob = wallets.bob; + let carol = wallets.carol; + + it("should parseUserKey", () => { + let aliceKey = parseUserKey(alice.pemFileText); + + assert.equal(aliceKey.hex(), alice.secretKeyHex); + assert.equal(aliceKey.generatePublicKey().toAddress().bech32(), alice.address.bech32()); + }); + + it("should parseValidatorKey", async () => { + await BLS.initIfNecessary(); + + let pem = `-----BEGIN PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208----- +N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBm +MWU0YzE3YTRjZDc3NDI0Nw== +-----END PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208-----`; + + let validatorKey = parseValidatorKey(pem); + + assert.equal(validatorKey.hex(), "7cff99bd671502db7d15bc8abc0c9a804fb925406fbdd50f1e4c17a4cd774247"); + assert.equal(validatorKey.generatePublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); + }); + + it("should parse multi-key PEM files", () => { + // The user PEM files encode both the seed and the pubkey in their payloads. + let payloadAlice = Buffer.from(alice.secretKeyHex + alice.address.hex()).toString("base64"); + let payloadBob = Buffer.from(bob.secretKeyHex + bob.address.hex()).toString("base64"); + let payloadCarol = Buffer.from(carol.secretKeyHex + carol.address.hex()).toString("base64"); + + let expected = [ + Buffer.concat([alice.secretKey, alice.address.pubkey()]), + Buffer.concat([bob.secretKey, bob.address.pubkey()]), + Buffer.concat([carol.secretKey, carol.address.pubkey()]) + ]; + + let trivialContent = `-----BEGIN PRIVATE KEY for alice +${payloadAlice} +-----END PRIVATE KEY for alice +-----BEGIN PRIVATE KEY for bob +${payloadBob} +-----END PRIVATE KEY for bob +-----BEGIN PRIVATE KEY for carol +${payloadCarol} +-----END PRIVATE KEY for carol +`; + + assert.deepEqual(parse(trivialContent, 64), expected); + + let contentWithWhitespaces = ` +-----BEGIN PRIVATE KEY for alice + ${payloadAlice} + -----END PRIVATE KEY for alice + + -----BEGIN PRIVATE KEY for bob + ${payloadBob} + -----END PRIVATE KEY for bob + -----BEGIN PRIVATE KEY for carol + + + ${payloadCarol} + -----END PRIVATE KEY for carol + `; + + assert.deepEqual(parse(contentWithWhitespaces, 64), expected); + }); + + it("should report parsing errors", () => { + let contentWithoutEnd = `-----BEGIN PRIVATE KEY for alice + NDEzZjQyNTc1ZjdmMjZmYWQzMzE3YTc3ODc3MTIxMmZkYjgwMjQ1ODUwOTgxZTQ4 + YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1ZDQy + MWYyNGMyOTE4MWU2Mzg4ODIyOGRjODFjYTYwZDY5ZTE=`; + + assert.throw(() => parseUserKey(contentWithoutEnd), errors.ErrBadPEM); + + let contentWithBadData = `-----BEGIN PRIVATE KEY for alice + NDEzZjQyNTc1ZjdmMjZmYWQzMzE3YTc3ODc3MTIxMmZkYjgwMjQ1ODUwOTgxZTQ4 + YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1Zfoo + -----END PRIVATE KEY for alice`; + + assert.throw(() => parseUserKey(contentWithBadData), errors.ErrBadPEM); + }); +}); diff --git a/src-wallet/pem.ts b/src-wallet/pem.ts index 5cb189143..45724efc8 100644 --- a/src-wallet/pem.ts +++ b/src-wallet/pem.ts @@ -1,5 +1,6 @@ -import { UserSecretKey } from "./userKeys"; -import { ValidatorSecretKey } from "./validatorKeys"; +import * as errors from "../errors"; +import { UserSecretKey, USER_PUBKEY_LENGTH, USER_SEED_LENGTH } from "./userKeys"; +import { ValidatorSecretKey, VALIDATOR_SECRETKEY_LENGTH } from "./validatorKeys"; export function parseUserKey(text: string, index: number = 0): UserSecretKey { let keys = parseUserKeys(text); @@ -7,8 +8,9 @@ export function parseUserKey(text: string, index: number = 0): UserSecretKey { } export function parseUserKeys(text: string): UserSecretKey[] { - let buffers = parse(text); - return buffers.map(buffer => new UserSecretKey(buffer.slice(0, 32))); + // The user PEM files encode both the seed and the pubkey in their payloads. + let buffers = parse(text, USER_SEED_LENGTH + USER_PUBKEY_LENGTH); + return buffers.map(buffer => new UserSecretKey(buffer.slice(0, USER_SEED_LENGTH))); } export function parseValidatorKey(text: string, index: number = 0): ValidatorSecretKey { @@ -17,12 +19,13 @@ export function parseValidatorKey(text: string, index: number = 0): ValidatorSec } export function parseValidatorKeys(text: string): ValidatorSecretKey[] { - let buffers = parse(text); + let buffers = parse(text, VALIDATOR_SECRETKEY_LENGTH); return buffers.map(buffer => new ValidatorSecretKey(buffer)); } -function parse(text: string): Buffer[] { - let lines = text.split(/\r?\n/); +export function parse(text: string, expectedLength: number): Buffer[] { + // Split by newlines, trim whitespace, then discard remaining empty lines. + let lines = text.split(/\r?\n/).map(line => line.trim()).filter(line => line.length > 0); let buffers: Buffer[] = []; let linesAccumulator: string[] = []; @@ -33,11 +36,21 @@ function parse(text: string): Buffer[] { let asBase64 = linesAccumulator.join(""); let asHex = Buffer.from(asBase64, "base64").toString(); let asBytes = Buffer.from(asHex, "hex"); + + if (asBytes.length != expectedLength) { + throw new errors.ErrBadPEM("incorrect key length"); + } + buffers.push(asBytes); + linesAccumulator = []; } else { linesAccumulator.push(line); } } + if (linesAccumulator.length != 0) { + throw new errors.ErrBadPEM("incorrect file structure"); + } + return buffers; } diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 0294818ab..54e5ab54f 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -3,20 +3,20 @@ import { Address } from "../address"; import { guardLength } from "../utils"; import { parseUserKey } from "./pem"; -const SEED_LENGTH = 32; -const PUBKEY_LENGTH = 32; +export const USER_SEED_LENGTH = 32; +export const USER_PUBKEY_LENGTH = 32; export class UserSecretKey { private readonly buffer: Buffer; constructor(buffer: Buffer) { - guardLength(buffer, SEED_LENGTH); + guardLength(buffer, USER_SEED_LENGTH); this.buffer = buffer; } static fromString(value: string): UserSecretKey { - guardLength(value, SEED_LENGTH * 2); + guardLength(value, USER_SEED_LENGTH * 2); let buffer = Buffer.from(value, "hex"); return new UserSecretKey(buffer); @@ -55,7 +55,7 @@ export class UserPublicKey { private readonly buffer: Buffer; constructor(buffer: Buffer) { - guardLength(buffer, PUBKEY_LENGTH); + guardLength(buffer, USER_PUBKEY_LENGTH); this.buffer = buffer; } diff --git a/src-wallet/validatorKeys.ts b/src-wallet/validatorKeys.ts index de085ff46..8b1ee75b0 100644 --- a/src-wallet/validatorKeys.ts +++ b/src-wallet/validatorKeys.ts @@ -1,10 +1,11 @@ +import * as errors from "../errors"; import { guardLength } from "../utils"; import { parseValidatorKey } from "./pem"; const bls = require('@elrondnetwork/bls-wasm'); -const SECRETKEY_LENGTH = 32; -const PUBKEY_LENGTH = 96; +export const VALIDATOR_SECRETKEY_LENGTH = 32; +export const VALIDATOR_PUBKEY_LENGTH = 96; export class BLS { private static isInitialized: boolean = false; @@ -18,6 +19,12 @@ export class BLS { BLS.isInitialized = true; } + + static guardInitialized() { + if (!BLS.isInitialized) { + throw new errors.ErrInvariantFailed("BLS modules are not initalized. Make sure that 'await BLS.initIfNecessary()' is called correctly."); + } + } } export class ValidatorSecretKey { @@ -25,7 +32,8 @@ export class ValidatorSecretKey { private readonly publicKey: any; constructor(buffer: Buffer) { - guardLength(buffer, SECRETKEY_LENGTH); + BLS.guardInitialized(); + guardLength(buffer, VALIDATOR_SECRETKEY_LENGTH); this.secretKey = new bls.SecretKey(); this.secretKey.setLittleEndian(Uint8Array.from(buffer)); @@ -60,7 +68,7 @@ export class ValidatorPublicKey { private readonly buffer: Buffer; constructor(buffer: Buffer) { - guardLength(buffer, PUBKEY_LENGTH); + guardLength(buffer, VALIDATOR_PUBKEY_LENGTH); this.buffer = buffer; } From 937001e00cf5fe9445b8ba96b555f0e7cd4adcfa Mon Sep 17 00:00:00 2001 From: Tudor Morar Date: Wed, 10 Feb 2021 16:03:21 +0200 Subject: [PATCH 018/118] Make tweetnacl use Uint8Array --- src-wallet/userKeys.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 54e5ab54f..7c9cf513d 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -27,13 +27,13 @@ export class UserSecretKey { } generatePublicKey(): UserPublicKey { - let keyPair = tweetnacl.sign.keyPair.fromSeed(this.buffer); + let keyPair = tweetnacl.sign.keyPair.fromSeed(new Uint8Array(this.buffer)); let buffer = Buffer.from(keyPair.publicKey); return new UserPublicKey(buffer); } sign(message: Buffer): Buffer { - let pair = tweetnacl.sign.keyPair.fromSeed(this.buffer); + let pair = tweetnacl.sign.keyPair.fromSeed(new Uint8Array(this.buffer)); let signingKey = pair.secretKey; let signature = tweetnacl.sign(new Uint8Array(message), signingKey); // "tweetnacl.sign()" returns the concatenated [signature, message], therfore we remove the appended message: From f369b669e79b95bc22305e12f5600d4983599b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 17 Feb 2021 09:59:54 +0200 Subject: [PATCH 019/118] Fixes necessary for automatic publishing of elrond-sdk-docs (#201) * Sketch workflow to build docs. * Fix typedoc configuration (upon upgrade to 0.20). * Try out various options for grouping components (in docs). --- src-wallet/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src-wallet/index.ts b/src-wallet/index.ts index 64dda6d9e..07fa2a2e2 100644 --- a/src-wallet/index.ts +++ b/src-wallet/index.ts @@ -1,3 +1,8 @@ +/** + * @packageDocumentation + * @module wallet + */ + export * from "./mnemonic"; export * from "./pem"; export * from "./userWallet"; From fc88864ba14d907fdc637926b4d4072a0aa9804b Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 17 Feb 2021 23:25:23 +0200 Subject: [PATCH 020/118] Fix some tests upon merge. --- src-wallet/users.spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index a33f25402..78cfa1601 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -166,11 +166,10 @@ describe("test user wallets", () => { gasPrice: new GasPrice(1000000000), gasLimit: new GasLimit(50000), data: new TransactionPayload("foo"), - chainID: new ChainID("1"), - version: new TransactionVersion(1) + chainID: new ChainID("1") }); await signer.sign(transaction); - assert.equal(transaction.signature.hex(), "c0bd2b3b33a07b9cc5ee7435228acb0936b3829c7008aacabceea35163e555e19a34def2c03a895cf36b0bcec30a7e11215c11efc0da29294a11234eb2b3b906"); + assert.equal(transaction.getSignature().hex(), "c0bd2b3b33a07b9cc5ee7435228acb0936b3829c7008aacabceea35163e555e19a34def2c03a895cf36b0bcec30a7e11215c11efc0da29294a11234eb2b3b906"); }); }); From 2607ddc959449fbe33a4144fa83dbc53584e3530 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 10 Mar 2021 23:32:14 +0200 Subject: [PATCH 021/118] Fix after review. --- src-wallet/userWallet.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 423cfaea5..d42c468ae 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -91,8 +91,6 @@ export class UserWallet { * - 350-360 ms in browser (Firefox), on a i3-8100 CPU @ 3.60GHz */ private static generateDerivedKey(password: Buffer, salt: Buffer, kdfparams: ScryptKeyDerivationParams): Buffer { - // Question for review: @ccorcoveanu, why not this implementation? - // https://nodejs.org/api/crypto.html#crypto_crypto_scrypt_password_salt_keylen_options_callback const derivedKey = scryptsy(password, salt, kdfparams.n, kdfparams.r, kdfparams.p, kdfparams.dklen); return derivedKey; } From e7344a6e49b0dcd0dcb8618bac2764d203ea35cc Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Fri, 18 Jun 2021 14:41:41 +0300 Subject: [PATCH 022/118] implemented encryption component --- src-wallet/crypto/constants.ts | 5 +++ src-wallet/crypto/decryptor.ts | 27 ++++++++++++++++ src-wallet/crypto/derivationParams.ts | 36 +++++++++++++++++++++ src-wallet/crypto/encrypt.spec.ts | 13 ++++++++ src-wallet/crypto/encryptedData.ts | 46 +++++++++++++++++++++++++++ src-wallet/crypto/encryptor.ts | 30 +++++++++++++++++ src-wallet/crypto/index.ts | 5 +++ src-wallet/crypto/randomness.ts | 15 +++++++++ 8 files changed, 177 insertions(+) create mode 100644 src-wallet/crypto/constants.ts create mode 100644 src-wallet/crypto/decryptor.ts create mode 100644 src-wallet/crypto/derivationParams.ts create mode 100644 src-wallet/crypto/encrypt.spec.ts create mode 100644 src-wallet/crypto/encryptedData.ts create mode 100644 src-wallet/crypto/encryptor.ts create mode 100644 src-wallet/crypto/index.ts create mode 100644 src-wallet/crypto/randomness.ts diff --git a/src-wallet/crypto/constants.ts b/src-wallet/crypto/constants.ts new file mode 100644 index 000000000..a9fe61a54 --- /dev/null +++ b/src-wallet/crypto/constants.ts @@ -0,0 +1,5 @@ +// In a future PR, improve versioning infrastructure for key-file objects in erdjs. +export const Version = 4; +export const CipherAlgorithm = "aes-128-ctr"; +export const DigestAlgorithm = "sha256"; +export const KeyDerivationFunction = "scrypt"; diff --git a/src-wallet/crypto/decryptor.ts b/src-wallet/crypto/decryptor.ts new file mode 100644 index 000000000..35baace91 --- /dev/null +++ b/src-wallet/crypto/decryptor.ts @@ -0,0 +1,27 @@ +import crypto from "crypto"; +import {EncryptedData} from "./encryptedData"; +import * as errors from "../errors"; +import { DigestAlgorithm } from "./constants"; + +export class Decryptor { + public 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 errors.ErrWallet("MAC mismatch, possibly wrong password"); + } + + const decipher = crypto.createDecipheriv(data.cipher, derivedKeyFirstHalf, iv); + + return Buffer.concat([decipher.update(ciphertext), decipher.final()]); + } +} diff --git a/src-wallet/crypto/derivationParams.ts b/src-wallet/crypto/derivationParams.ts new file mode 100644 index 000000000..134a95343 --- /dev/null +++ b/src-wallet/crypto/derivationParams.ts @@ -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); + } +} diff --git a/src-wallet/crypto/encrypt.spec.ts b/src-wallet/crypto/encrypt.spec.ts new file mode 100644 index 000000000..bd15c3f93 --- /dev/null +++ b/src-wallet/crypto/encrypt.spec.ts @@ -0,0 +1,13 @@ +import { assert } from "chai"; +import { Encryptor } from "./encryptor"; +import { Decryptor } from "./decryptor"; + +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')); + }); +}); \ No newline at end of file diff --git a/src-wallet/crypto/encryptedData.ts b/src-wallet/crypto/encryptedData.ts new file mode 100644 index 000000000..7477ca65e --- /dev/null +++ b/src-wallet/crypto/encryptedData.ts @@ -0,0 +1,46 @@ +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) { + 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, + } + }; + } +} \ No newline at end of file diff --git a/src-wallet/crypto/encryptor.ts b/src-wallet/crypto/encryptor.ts new file mode 100644 index 000000000..e329dbb9c --- /dev/null +++ b/src-wallet/crypto/encryptor.ts @@ -0,0 +1,30 @@ +import crypto from "crypto"; +import { Randomness } from "./randomness"; +import { ScryptKeyDerivationParams } from "./derivationParams"; +import { CipherAlgorithm, DigestAlgorithm, Version, KeyDerivationFunction } from "./constants"; +import {EncryptedData} from "./encryptedData"; + +export class Encryptor { + public static encrypt(data: Buffer, password: string, randomness: Randomness = 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: Version, + 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') + }); + } +} diff --git a/src-wallet/crypto/index.ts b/src-wallet/crypto/index.ts new file mode 100644 index 000000000..d674cb91f --- /dev/null +++ b/src-wallet/crypto/index.ts @@ -0,0 +1,5 @@ +export * from "./constants"; +export * from "./encryptor"; +export * from "./decryptor"; +export * from "./encryptedData"; +export * from "./randomness"; diff --git a/src-wallet/crypto/randomness.ts b/src-wallet/crypto/randomness.ts new file mode 100644 index 000000000..f045dc676 --- /dev/null +++ b/src-wallet/crypto/randomness.ts @@ -0,0 +1,15 @@ +import nacl from "tweetnacl"; +import {v4 as uuidv4} from "uuid"; +const crypto = require("crypto"); + +export class Randomness { + salt: Buffer; + iv: Buffer; + id: string; + + constructor(init?: Partial) { + this.salt = init?.salt || Buffer.from(nacl.randomBytes(32)); + this.iv = init?.iv || Buffer.from(nacl.randomBytes(16)); + this.id = init?.id || uuidv4({ random: crypto.randomBytes(16) }); + } +} From 702268d8a031e05bcb9ee16c9109e9935a0fb145 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Fri, 18 Jun 2021 14:41:41 +0300 Subject: [PATCH 023/118] implemented encryption component --- src-wallet/userWallet.ts | 125 ++++++++++----------------------------- src-wallet/users.spec.ts | 3 +- 2 files changed, 34 insertions(+), 94 deletions(-) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index d42c468ae..22d581829 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -1,22 +1,10 @@ -import * as errors from "../errors"; -import nacl from "tweetnacl"; import { UserPublicKey, UserSecretKey } from "./userKeys"; -const crypto = require("crypto"); -import { v4 as uuidv4 } from "uuid"; -import scryptsy from "scryptsy"; - -// In a future PR, improve versioning infrastructure for key-file objects in erdjs. -const Version = 4; -const CipherAlgorithm = "aes-128-ctr"; -const DigestAlgorithm = "sha256"; -const KeyDerivationFunction = "scrypt"; +import { EncryptedData, Encryptor, Decryptor, CipherAlgorithm, Version, KeyDerivationFunction, Randomness } from "../crypto"; +import {ScryptKeyDerivationParams} from "../crypto/derivationParams"; export class UserWallet { private readonly publicKey: UserPublicKey; - private readonly randomness: Randomness; - private readonly ciphertext: Buffer; - private readonly mac: Buffer; - private readonly kdfparams: ScryptKeyDerivationParams; + private readonly encryptedData: EncryptedData; /** * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L76 @@ -30,21 +18,9 @@ export class UserWallet { * passed through a password-based key derivation function (kdf). */ constructor(secretKey: UserSecretKey, password: string, randomness: Randomness = new Randomness()) { - const kdParams = new ScryptKeyDerivationParams(); - const derivedKey = UserWallet.generateDerivedKey(Buffer.from(password), randomness.salt, kdParams); - const derivedKeyFirstHalf = derivedKey.slice(0, 16); - const derivedKeySecondHalf = derivedKey.slice(16, 32); - const cipher = crypto.createCipheriv(CipherAlgorithm, derivedKeyFirstHalf, randomness.iv); - const text = Buffer.concat([secretKey.valueOf(), secretKey.generatePublicKey().valueOf()]); - const ciphertext = Buffer.concat([cipher.update(text), cipher.final()]); - const mac = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest(); - + this.encryptedData = Encryptor.encrypt(text, password, randomness); this.publicKey = secretKey.generatePublicKey(); - this.randomness = randomness; - this.ciphertext = ciphertext; - this.mac = mac; - this.kdfparams = kdParams; } /** @@ -58,24 +34,9 @@ export class UserWallet { * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { - const kdfparams = keyFileObject.crypto.kdfparams; - const salt = Buffer.from(kdfparams.salt, "hex"); - const iv = Buffer.from(keyFileObject.crypto.cipherparams.iv, "hex"); - const ciphertext = Buffer.from(keyFileObject.crypto.ciphertext, "hex"); - const derivedKey = UserWallet.generateDerivedKey(Buffer.from(password), salt, kdfparams); - const derivedKeyFirstHalf = derivedKey.slice(0, 16); - const derivedKeySecondHalf = derivedKey.slice(16, 32); - - const computedMAC = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest(); - const actualMAC = keyFileObject.crypto.mac; - - if (computedMAC.toString("hex") !== actualMAC) { - throw new errors.ErrWallet("MAC mismatch, possibly wrong password"); - } - - const decipher = crypto.createDecipheriv(keyFileObject.crypto.cipher, derivedKeyFirstHalf, iv); + const encryptedData = UserWallet.ecFromJSON(keyFileObject) - let text = Buffer.concat([decipher.update(ciphertext), decipher.final()]); + let text = Decryptor.decrypt(encryptedData, password); while (text.length < 32) { let zeroPadding = Buffer.from([0x00]); text = Buffer.concat([zeroPadding, text]); @@ -85,14 +46,23 @@ export class UserWallet { return new UserSecretKey(seed); } - /** - * 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 - */ - private static generateDerivedKey(password: Buffer, salt: Buffer, kdfparams: ScryptKeyDerivationParams): Buffer { - const derivedKey = scryptsy(password, salt, kdfparams.n, kdfparams.r, kdfparams.p, kdfparams.dklen); - return derivedKey; + static ecFromJSON(keyfileObject: any): EncryptedData { + return new EncryptedData({ + version: Version, + id: keyfileObject.id, + cipher: keyfileObject.crypto.cipher, + ciphertext: keyfileObject.crypto.ciphertext, + iv: keyfileObject.crypto.cipherparams.iv, + kdf: keyfileObject.crypto.kdf, + kdfparams: new ScryptKeyDerivationParams( + keyfileObject.crypto.kdfparams.n, + keyfileObject.crypto.kdfparams.r, + keyfileObject.crypto.kdfparams.p, + keyfileObject.crypto.kdfparams.dklen + ), + salt: keyfileObject.crypto.kdfparams.salt, + mac: keyfileObject.crypto.mac, + }); } /** @@ -101,54 +71,23 @@ export class UserWallet { toJSON(): any { return { version: Version, - id: this.randomness.id, + id: this.encryptedData.id, address: this.publicKey.hex(), bech32: this.publicKey.toAddress().toString(), crypto: { - ciphertext: this.ciphertext.toString("hex"), - cipherparams: { iv: this.randomness.iv.toString("hex") }, + ciphertext: this.encryptedData.ciphertext, + cipherparams: { iv: this.encryptedData.iv }, cipher: CipherAlgorithm, kdf: KeyDerivationFunction, kdfparams: { - dklen: this.kdfparams.dklen, - salt: this.randomness.salt.toString("hex"), - n: this.kdfparams.n, - r: this.kdfparams.r, - p: this.kdfparams.p + dklen: this.encryptedData.kdfparams.dklen, + salt: this.encryptedData.salt, + n: this.encryptedData.kdfparams.n, + r: this.encryptedData.kdfparams.r, + p: this.encryptedData.kdfparams.p }, - mac: this.mac.toString("hex"), + mac: this.encryptedData.mac, } }; } } - -class ScryptKeyDerivationParams { - /** - * numIterations - */ - n = 4096; - - /** - * memFactor - */ - r = 8; - - /** - * pFactor - */ - p = 1; - - dklen = 32; -} - -export class Randomness { - salt: Buffer; - iv: Buffer; - id: string; - - constructor(init?: Partial) { - this.salt = init?.salt || Buffer.from(nacl.randomBytes(32)); - this.iv = init?.iv || Buffer.from(nacl.randomBytes(16)); - this.id = init?.id || uuidv4({ random: crypto.randomBytes(16) }); - } -} diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 78cfa1601..4f57c11f0 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -3,7 +3,8 @@ import { assert } from "chai"; import { TestWallets } from "../testutils"; import { UserSecretKey } from "./userKeys"; import { Mnemonic } from "./mnemonic"; -import { UserWallet, Randomness } from "./userWallet"; +import { UserWallet } from "./userWallet"; +import { Randomness } from "../crypto"; import { Address } from "../address"; import { UserSigner } from "./userSigner"; import { Transaction } from "../transaction"; From 77f33066d921843fdc9fae2f50fdeb5ca8b66377 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Tue, 22 Jun 2021 13:49:27 +0300 Subject: [PATCH 024/118] add json decoed function for EncryptedData --- src-wallet/crypto/encrypt.spec.ts | 9 +++++++++ src-wallet/crypto/encryptedData.ts | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src-wallet/crypto/encrypt.spec.ts b/src-wallet/crypto/encrypt.spec.ts index bd15c3f93..9b7eaf0af 100644 --- a/src-wallet/crypto/encrypt.spec.ts +++ b/src-wallet/crypto/encrypt.spec.ts @@ -1,6 +1,7 @@ import { assert } from "chai"; import { Encryptor } from "./encryptor"; import { Decryptor } from "./decryptor"; +import { EncryptedData } from "./encryptedData"; describe("test address", () => { it("encrypts/decrypts", () => { @@ -10,4 +11,12 @@ describe("test address", () => { 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"); + }); }); \ No newline at end of file diff --git a/src-wallet/crypto/encryptedData.ts b/src-wallet/crypto/encryptedData.ts index 7477ca65e..14ff97ae9 100644 --- a/src-wallet/crypto/encryptedData.ts +++ b/src-wallet/crypto/encryptedData.ts @@ -43,4 +43,23 @@ export class EncryptedData { } }; } + + 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, + }); + } } \ No newline at end of file From 114519229486cae91817309a1aea96c65db1a0a1 Mon Sep 17 00:00:00 2001 From: Claudiu-Marcel Bruda Date: Mon, 28 Jun 2021 17:24:14 +0300 Subject: [PATCH 025/118] Add contract wrapper --- src-wallet/pem.spec.ts | 18 +++++++++--------- src-wallet/pem.ts | 2 +- src-wallet/users.spec.ts | 23 ++++++++++++----------- src-wallet/validators.spec.ts | 2 -- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src-wallet/pem.spec.ts b/src-wallet/pem.spec.ts index 591e379b8..89fb76469 100644 --- a/src-wallet/pem.spec.ts +++ b/src-wallet/pem.spec.ts @@ -1,33 +1,33 @@ import * as errors from "../errors"; import { assert } from "chai"; -import { TestWallets } from "../testutils"; +import { loadTestWallets, TestWallet } from "../testutils"; import { parse, parseUserKey, parseValidatorKey } from "./pem"; import { BLS } from "."; import { Buffer } from "buffer"; describe("test PEMs", () => { - let wallets = new TestWallets(); - let alice = wallets.alice; - let bob = wallets.bob; - let carol = wallets.carol; + let alice: TestWallet, bob: TestWallet, carol: TestWallet; + before(async function () { + ({ alice, bob, carol } = await loadTestWallets()); + }); it("should parseUserKey", () => { let aliceKey = parseUserKey(alice.pemFileText); - + assert.equal(aliceKey.hex(), alice.secretKeyHex); assert.equal(aliceKey.generatePublicKey().toAddress().bech32(), alice.address.bech32()); }); it("should parseValidatorKey", async () => { await BLS.initIfNecessary(); - + let pem = `-----BEGIN PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208----- N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBm MWU0YzE3YTRjZDc3NDI0Nw== -----END PRIVATE KEY for e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208-----`; let validatorKey = parseValidatorKey(pem); - + assert.equal(validatorKey.hex(), "7cff99bd671502db7d15bc8abc0c9a804fb925406fbdd50f1e4c17a4cd774247"); assert.equal(validatorKey.generatePublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); }); @@ -55,7 +55,7 @@ ${payloadCarol} -----END PRIVATE KEY for carol `; - assert.deepEqual(parse(trivialContent, 64), expected); + assert.deepEqual(parse(trivialContent, 64), expected); let contentWithWhitespaces = ` -----BEGIN PRIVATE KEY for alice diff --git a/src-wallet/pem.ts b/src-wallet/pem.ts index 45724efc8..546ab0cdd 100644 --- a/src-wallet/pem.ts +++ b/src-wallet/pem.ts @@ -38,7 +38,7 @@ export function parse(text: string, expectedLength: number): Buffer[] { let asBytes = Buffer.from(asHex, "hex"); if (asBytes.length != expectedLength) { - throw new errors.ErrBadPEM("incorrect key length"); + throw new errors.ErrBadPEM(`incorrect key length: expected ${expectedLength}, found ${asBytes.length}`); } buffers.push(asBytes); diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 78cfa1601..6ffff61f7 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -1,6 +1,6 @@ import * as errors from "../errors"; import { assert } from "chai"; -import { TestWallets } from "../testutils"; +import { loadMnemonic, loadPassword, loadTestWallets, TestWallet } from "../testutils"; import { UserSecretKey } from "./userKeys"; import { Mnemonic } from "./mnemonic"; import { UserWallet, Randomness } from "./userWallet"; @@ -13,11 +13,12 @@ import { ChainID, GasLimit, GasPrice, TransactionVersion } from "../networkParam import { TransactionPayload } from "../transactionPayload"; describe("test user wallets", () => { - let wallets = new TestWallets(); - let alice = wallets.alice; - let bob = wallets.bob; - let carol = wallets.carol; - let password = wallets.password; + let alice: TestWallet, bob: TestWallet, carol: TestWallet; + let password: string; + before(async function () { + ({ alice, bob, carol } = await loadTestWallets()); + password = await loadPassword(); + }); it("should generate mnemonic", () => { let mnemonic = Mnemonic.generate(); @@ -25,8 +26,8 @@ describe("test user wallets", () => { assert.lengthOf(words, 24); }); - it("should derive keys", () => { - let mnemonic = Mnemonic.fromString(wallets.mnemonic); + it("should derive keys", async () => { + let mnemonic = Mnemonic.fromString(await loadMnemonic()); assert.equal(mnemonic.deriveKey(0).hex(), alice.secretKeyHex); assert.equal(mnemonic.deriveKey(1).hex(), bob.secretKeyHex); @@ -34,7 +35,7 @@ describe("test user wallets", () => { }); it("should create secret key", () => { - let keyHex = wallets.alice.secretKeyHex; + let keyHex = alice.secretKeyHex; let fromBuffer = new UserSecretKey(Buffer.from(keyHex, "hex")); let fromHex = UserSecretKey.fromString(keyHex); @@ -138,7 +139,7 @@ describe("test user wallets", () => { assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); assert.equal(transaction.getSignature().hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); - + // Without data field transaction = new Transaction({ nonce: new Nonce(8), @@ -158,7 +159,7 @@ describe("test user wallets", () => { it("should sign transactions using PEM files", async () => { let signer = UserSigner.fromPem(alice.pemFileText); - + let transaction = new Transaction({ nonce: new Nonce(0), value: Balance.Zero(), diff --git a/src-wallet/validators.spec.ts b/src-wallet/validators.spec.ts index e737e2f5f..609098cca 100644 --- a/src-wallet/validators.spec.ts +++ b/src-wallet/validators.spec.ts @@ -1,9 +1,7 @@ import { assert } from "chai"; -import { TestWallets } from "../testutils"; import { BLS, ValidatorSecretKey } from "./validatorKeys"; describe("test validator keys", () => { - let wallets = new TestWallets(); it("should create secret key and sign a message", async () => { await BLS.initIfNecessary(); From 1f93e0dd790ae40c4141eb51b3ed14de6a9e37a1 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Tue, 29 Jun 2021 14:06:15 +0300 Subject: [PATCH 026/118] added empty line --- src-wallet/crypto/encrypt.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/crypto/encrypt.spec.ts b/src-wallet/crypto/encrypt.spec.ts index 9b7eaf0af..b076b3111 100644 --- a/src-wallet/crypto/encrypt.spec.ts +++ b/src-wallet/crypto/encrypt.spec.ts @@ -19,4 +19,4 @@ describe("test address", () => { assert.deepEqual(decodedData, encryptedData, "invalid decoded data"); }); -}); \ No newline at end of file +}); From a4b511e0354f5f860f1bdf90fb0303f4d96cc9b7 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Tue, 29 Jun 2021 14:07:26 +0300 Subject: [PATCH 027/118] added empty line --- src-wallet/crypto/encryptedData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/crypto/encryptedData.ts b/src-wallet/crypto/encryptedData.ts index 14ff97ae9..b882a5707 100644 --- a/src-wallet/crypto/encryptedData.ts +++ b/src-wallet/crypto/encryptedData.ts @@ -62,4 +62,4 @@ export class EncryptedData { mac: data.crypto.mac, }); } -} \ No newline at end of file +} From a0950fc7be5f2d51b6e635fb63ac4cf50167f3f2 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Thu, 1 Jul 2021 10:37:18 +0300 Subject: [PATCH 028/118] typo fix --- src-wallet/userWallet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 22d581829..c0f7064d8 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -34,7 +34,7 @@ export class UserWallet { * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { - const encryptedData = UserWallet.ecFromJSON(keyFileObject) + const encryptedData = UserWallet.edFromJSON(keyFileObject) let text = Decryptor.decrypt(encryptedData, password); while (text.length < 32) { @@ -46,7 +46,7 @@ export class UserWallet { return new UserSecretKey(seed); } - static ecFromJSON(keyfileObject: any): EncryptedData { + static edFromJSON(keyfileObject: any): EncryptedData { return new EncryptedData({ version: Version, id: keyfileObject.id, From 0cd37007832fd98d1a94b8cc925a3b5b6be91a5d Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Thu, 15 Jul 2021 10:01:53 +0300 Subject: [PATCH 029/118] message signing progress --- src-wallet/userKeys.ts | 13 +++++++++++++ src-wallet/userVerifier.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src-wallet/userVerifier.ts diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 7c9cf513d..415a9ea19 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -2,6 +2,8 @@ import * as tweetnacl from "tweetnacl"; import { Address } from "../address"; import { guardLength } from "../utils"; import { parseUserKey } from "./pem"; +import {SignableMessage} from "../signableMessage"; +import {Logger} from "../logger"; export const USER_SEED_LENGTH = 32; export const USER_PUBKEY_LENGTH = 32; @@ -60,6 +62,17 @@ export class UserPublicKey { this.buffer = buffer; } + verify(message: Buffer, signature: Buffer): boolean { + try { + const unopenedMessage = Buffer.concat([Buffer.from(message.signature.hex(), "hex"), message]); + const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.address.pubkey()); + return unsignedMessage != null; + } catch (err) { + Logger.error(err); + return false; + } + } + hex(): string { return this.buffer.toString("hex"); } diff --git a/src-wallet/userVerifier.ts b/src-wallet/userVerifier.ts new file mode 100644 index 000000000..a2a8e6814 --- /dev/null +++ b/src-wallet/userVerifier.ts @@ -0,0 +1,32 @@ +import { IVerifier } from "../interface"; +import { SignableMessage } from "../signableMessage"; +import * as errors from "../errors"; +import { Address } from "../address"; +import * as tweetnacl from "tweetnacl"; +import {Logger} from "../logger"; + +/** + * ed25519 signature verification + */ +export class UserVerifier implements IVerifier { + + address: Address; + constructor(address: Address) { + this.address = address; + } + + /** + * Verify a message's signature. + * @param message the message to be verified. + */ + async verify(message: SignableMessage): Promise { + try { + const unopenedMessage = Buffer.concat([Buffer.from(message.signature.hex(), "hex"), message.serializeForSigning()]); + const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.address.pubkey()); + return unsignedMessage != null; + } catch (err) { + Logger.error(err); + return false; + } + } +} From cf5774084f37d47e43a557f00fb198cdce10e658 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Thu, 15 Jul 2021 12:34:28 +0300 Subject: [PATCH 030/118] finished message signing implementation --- src-wallet/userKeys.ts | 4 ++-- src-wallet/userVerifier.ts | 33 +++++++++++++++------------------ src-wallet/users.spec.ts | 19 +++++++++++++++++-- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 415a9ea19..1bb44f5e1 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -64,8 +64,8 @@ export class UserPublicKey { verify(message: Buffer, signature: Buffer): boolean { try { - const unopenedMessage = Buffer.concat([Buffer.from(message.signature.hex(), "hex"), message]); - const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.address.pubkey()); + const unopenedMessage = Buffer.concat([signature, message]); + const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.buffer); return unsignedMessage != null; } catch (err) { Logger.error(err); diff --git a/src-wallet/userVerifier.ts b/src-wallet/userVerifier.ts index a2a8e6814..d39bfa885 100644 --- a/src-wallet/userVerifier.ts +++ b/src-wallet/userVerifier.ts @@ -1,32 +1,29 @@ -import { IVerifier } from "../interface"; -import { SignableMessage } from "../signableMessage"; -import * as errors from "../errors"; -import { Address } from "../address"; -import * as tweetnacl from "tweetnacl"; -import {Logger} from "../logger"; +import {IVerifiable, IVerifier} from "../interface"; +import {Address} from "../address"; +import {UserPublicKey} from "./userKeys"; /** * ed25519 signature verification */ export class UserVerifier implements IVerifier { - address: Address; - constructor(address: Address) { - this.address = address; + publicKey: UserPublicKey; + constructor(publicKey: UserPublicKey) { + this.publicKey = publicKey; + } + + static fromAddress(address: Address): IVerifier { + let publicKey = new UserPublicKey(address.pubkey()); + return new UserVerifier(publicKey); } /** * Verify a message's signature. * @param message the message to be verified. */ - async verify(message: SignableMessage): Promise { - try { - const unopenedMessage = Buffer.concat([Buffer.from(message.signature.hex(), "hex"), message.serializeForSigning()]); - const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.address.pubkey()); - return unsignedMessage != null; - } catch (err) { - Logger.error(err); - return false; - } + verify(message: IVerifiable): boolean { + return this.publicKey.verify( + message.serializeForSigning(this.publicKey.toAddress()), + Buffer.from(message.getSignature().hex(), 'hex')); } } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 4f57c11f0..d42a42155 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -10,8 +10,10 @@ import { UserSigner } from "./userSigner"; import { Transaction } from "../transaction"; import { Nonce } from "../nonce"; import { Balance } from "../balance"; -import { ChainID, GasLimit, GasPrice, TransactionVersion } from "../networkParams"; +import { ChainID, GasLimit, GasPrice } from "../networkParams"; import { TransactionPayload } from "../transactionPayload"; +import {UserVerifier} from "./userVerifier"; +import {SignableMessage} from "../signableMessage"; describe("test user wallets", () => { let wallets = new TestWallets(); @@ -120,6 +122,7 @@ describe("test user wallets", () => { it("should sign transactions", async () => { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); + let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); let sender = new Address("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); let receiver = new Address("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"); @@ -139,7 +142,7 @@ describe("test user wallets", () => { assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); assert.equal(transaction.getSignature().hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); - + assert.isTrue(verifier.verify(transaction)); // Without data field transaction = new Transaction({ nonce: new Nonce(8), @@ -173,4 +176,16 @@ describe("test user wallets", () => { await signer.sign(transaction); assert.equal(transaction.getSignature().hex(), "c0bd2b3b33a07b9cc5ee7435228acb0936b3829c7008aacabceea35163e555e19a34def2c03a895cf36b0bcec30a7e11215c11efc0da29294a11234eb2b3b906"); }); + + it("signs a general message", function() { + let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); + let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); + const message = new SignableMessage({ + message: Buffer.from("hello world") + }); + + signer.sign(message); + assert.isNotEmpty(message.signature); + assert.isTrue(verifier.verify(message)); + }); }); From 57c6a7ab14eadff9e7639e7d438bb87716daf663 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Tue, 20 Jul 2021 12:14:38 +0300 Subject: [PATCH 031/118] exported user verifier --- src-wallet/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src-wallet/index.ts b/src-wallet/index.ts index 07fa2a2e2..acf9c06b9 100644 --- a/src-wallet/index.ts +++ b/src-wallet/index.ts @@ -9,4 +9,5 @@ export * from "./userWallet"; export * from "./userKeys"; export * from "./validatorKeys"; export * from "./userSigner"; +export * from "./userVerifier"; export * from "./validatorSigner"; From 58bc47e9a5ea86c8818a2c65e337c16a57031122 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Mon, 14 Mar 2022 11:04:14 +0200 Subject: [PATCH 032/118] Fix remaining imports. --- src-wallet/pem.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/pem.spec.ts b/src-wallet/pem.spec.ts index 89fb76469..f44260e6c 100644 --- a/src-wallet/pem.spec.ts +++ b/src-wallet/pem.spec.ts @@ -2,8 +2,8 @@ import * as errors from "../errors"; import { assert } from "chai"; import { loadTestWallets, TestWallet } from "../testutils"; import { parse, parseUserKey, parseValidatorKey } from "./pem"; -import { BLS } from "."; import { Buffer } from "buffer"; +import { BLS } from "./validatorKeys"; describe("test PEMs", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; From b2d88da88e719ae95ccc6ecd9fa445802cb5fdd6 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 23 Mar 2022 13:52:41 +0200 Subject: [PATCH 033/118] Partial fixes after extraction. --- src-wallet/address.ts | 71 ++++++++++++++++++++++++++++++++++ src-wallet/crypto/decryptor.ts | 6 +-- src-wallet/errors.ts | 56 +++++++++++++++++++++++++++ src-wallet/index.ts | 5 --- src-wallet/interface.ts | 59 ++++++++++++++++++++++++++++ src-wallet/mnemonic.ts | 4 +- src-wallet/pem.spec.ts | 6 +-- src-wallet/pem.ts | 6 +-- src-wallet/signature.ts | 14 +++++++ src-wallet/userKeys.ts | 8 ++-- src-wallet/userSigner.ts | 12 +++--- src-wallet/userVerifier.ts | 6 +-- src-wallet/userWallet.ts | 4 +- src-wallet/users.spec.ts | 6 +-- src-wallet/utils.ts | 9 +++++ src-wallet/validatorKeys.ts | 6 +-- src-wallet/validatorSigner.ts | 4 +- 17 files changed, 242 insertions(+), 40 deletions(-) create mode 100644 src-wallet/address.ts create mode 100644 src-wallet/errors.ts create mode 100644 src-wallet/interface.ts create mode 100644 src-wallet/signature.ts create mode 100644 src-wallet/utils.ts diff --git a/src-wallet/address.ts b/src-wallet/address.ts new file mode 100644 index 000000000..a31749c08 --- /dev/null +++ b/src-wallet/address.ts @@ -0,0 +1,71 @@ +import * as bech32 from "bech32"; + +/** + * The human-readable-part of the bech32 addresses. + */ +const HRP = "erd"; + +/** + * An Elrond Address, as an immutable object. + */ +export class Address { + private readonly buffer: Buffer; + + public constructor(buffer: Buffer) { + this.buffer = buffer; + } + + /** + * Returns the hex representation of the address (pubkey) + */ + hex(): string { + return this.buffer.toString("hex"); + } + + /** + * Returns the bech32 representation of the address + */ + bech32(): string { + let words = bech32.toWords(this.pubkey()); + let address = bech32.encode(HRP, words); + return address; + } + + /** + * Returns the pubkey as raw bytes (buffer) + */ + pubkey(): Buffer { + return this.buffer; + } + + /** + * Returns whether the address is empty. + */ + isEmpty() { + return !this.buffer; + } + + /** + * Compares the address to another address + */ + equals(other: Address): boolean { + return this.buffer.compare(other.buffer) == 0; + } + + /** + * Returns the bech32 representation of the address + */ + toString(): string { + return this.bech32(); + } + + /** + * Converts the address to a pretty, plain JavaScript object. + */ + toJSON(): object { + return { + bech32: this.bech32(), + pubkey: this.hex() + }; + } +} diff --git a/src-wallet/crypto/decryptor.ts b/src-wallet/crypto/decryptor.ts index 35baace91..6a859d1d3 100644 --- a/src-wallet/crypto/decryptor.ts +++ b/src-wallet/crypto/decryptor.ts @@ -1,7 +1,7 @@ import crypto from "crypto"; -import {EncryptedData} from "./encryptedData"; -import * as errors from "../errors"; +import { EncryptedData } from "./encryptedData"; import { DigestAlgorithm } from "./constants"; +import { ErrWrongPassword } from "../errors"; export class Decryptor { public static decrypt(data: EncryptedData, password: string): Buffer { @@ -17,7 +17,7 @@ export class Decryptor { const actualMAC = data.mac; if (computedMAC.toString("hex") !== actualMAC) { - throw new errors.ErrWallet("MAC mismatch, possibly wrong password"); + throw new ErrWrongPassword("MAC mismatch"); } const decipher = crypto.createDecipheriv(data.cipher, derivedKeyFirstHalf, iv); diff --git a/src-wallet/errors.ts b/src-wallet/errors.ts new file mode 100644 index 000000000..e27b36bc9 --- /dev/null +++ b/src-wallet/errors.ts @@ -0,0 +1,56 @@ +/** + * The base class for `erdjs` exceptions (errors). + */ +export class Err extends Error { + inner: Error | undefined = undefined; + + public constructor(message: string, inner?: Error) { + super(message); + this.inner = inner; + } +} + +/** + * Signals that an invariant failed. + */ +export class ErrInvariantFailed extends Err { + public constructor(message: string) { + super(`"Invariant failed: ${message}`); + } +} + +/** + * Signals a wrong mnemonic format. + */ +export class ErrWrongMnemonic extends Err { + public constructor() { + super("Wrong mnemonic format"); + } +} + +/** + * Signals a wrong password. + */ + export class ErrWrongPassword extends Err { + public constructor(message: string) { + super(`Possibly wrong password: ${message}`); + } +} + +/** + * Signals a bad PEM file. + */ +export class ErrBadPEM extends Err { + public constructor(message?: string) { + super(message ? `Bad PEM: ${message}` : `Bad PEM`); + } +} + +/** + * Signals an error related to signing a message (a transaction). + */ +export class ErrSignerCannotSign extends Err { + public constructor(inner: Error) { + super(`Cannot sign`, inner); + } +} diff --git a/src-wallet/index.ts b/src-wallet/index.ts index acf9c06b9..c5d518dfd 100644 --- a/src-wallet/index.ts +++ b/src-wallet/index.ts @@ -1,8 +1,3 @@ -/** - * @packageDocumentation - * @module wallet - */ - export * from "./mnemonic"; export * from "./pem"; export * from "./userWallet"; diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts new file mode 100644 index 000000000..a721048c7 --- /dev/null +++ b/src-wallet/interface.ts @@ -0,0 +1,59 @@ +export interface IAddress { + hex(): string; + bech32(): string; +} + +export interface ISignature { + hex(): string; +} + +/** + * An interface that defines a signing-capable object. + */ +export interface ISigner { + /** + * Gets the {@link Address} of the signer. + */ + getAddress(): IAddress; + + /** + * Signs a message (e.g. a transaction). + */ + sign(signable: ISignable): Promise; +} + +export interface IVerifier { + verify(message: IVerifiable): boolean; +} + +/** + * An interface that defines a signable object (e.g. a transaction). + */ +export interface ISignable { + /** + * Returns the signable object in its raw form - a sequence of bytes to be signed. + */ + serializeForSigning(signedBy: IAddress): Buffer; + + /** + * Applies the computed signature on the object itself. + * + * @param signature The computed signature + * @param signedBy The address of the {@link ISignature} + */ + applySignature(signature: ISignature, signedBy: IAddress): void; +} + +/** + * Interface that defines a signed and verifiable object + */ +export interface IVerifiable { + /** + * Returns the signature that should be verified + */ + getSignature(): ISignature; + /** + * Returns the signable object in its raw form - a sequence of bytes to be verified. + */ + serializeForSigning(signedBy?: IAddress): Buffer; +} diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts index 69c298d66..b9dba56ae 100644 --- a/src-wallet/mnemonic.ts +++ b/src-wallet/mnemonic.ts @@ -1,7 +1,7 @@ -import * as errors from "../errors"; import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from "bip39"; import { UserSecretKey } from "./userKeys"; import { derivePath } from "ed25519-hd-key"; +import { ErrWrongMnemonic } from "./errors"; const MNEMONIC_STRENGTH = 256; const BIP44_DERIVATION_PREFIX = "m/44'/508'/0'/0'"; @@ -29,7 +29,7 @@ export class Mnemonic { let isValid = validateMnemonic(text); if (!isValid) { - throw new errors.ErrWrongMnemonic(); + throw new ErrWrongMnemonic(); } } diff --git a/src-wallet/pem.spec.ts b/src-wallet/pem.spec.ts index f44260e6c..2b6a7e74d 100644 --- a/src-wallet/pem.spec.ts +++ b/src-wallet/pem.spec.ts @@ -1,9 +1,9 @@ -import * as errors from "../errors"; import { assert } from "chai"; import { loadTestWallets, TestWallet } from "../testutils"; import { parse, parseUserKey, parseValidatorKey } from "./pem"; import { Buffer } from "buffer"; import { BLS } from "./validatorKeys"; +import { ErrBadPEM } from "./errors"; describe("test PEMs", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; @@ -81,13 +81,13 @@ ${payloadCarol} YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1ZDQy MWYyNGMyOTE4MWU2Mzg4ODIyOGRjODFjYTYwZDY5ZTE=`; - assert.throw(() => parseUserKey(contentWithoutEnd), errors.ErrBadPEM); + assert.throw(() => parseUserKey(contentWithoutEnd), ErrBadPEM); let contentWithBadData = `-----BEGIN PRIVATE KEY for alice NDEzZjQyNTc1ZjdmMjZmYWQzMzE3YTc3ODc3MTIxMmZkYjgwMjQ1ODUwOTgxZTQ4 YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1Zfoo -----END PRIVATE KEY for alice`; - assert.throw(() => parseUserKey(contentWithBadData), errors.ErrBadPEM); + assert.throw(() => parseUserKey(contentWithBadData), ErrBadPEM); }); }); diff --git a/src-wallet/pem.ts b/src-wallet/pem.ts index 546ab0cdd..f9add5494 100644 --- a/src-wallet/pem.ts +++ b/src-wallet/pem.ts @@ -1,4 +1,4 @@ -import * as errors from "../errors"; +import { ErrBadPEM } from "./errors"; import { UserSecretKey, USER_PUBKEY_LENGTH, USER_SEED_LENGTH } from "./userKeys"; import { ValidatorSecretKey, VALIDATOR_SECRETKEY_LENGTH } from "./validatorKeys"; @@ -38,7 +38,7 @@ export function parse(text: string, expectedLength: number): Buffer[] { let asBytes = Buffer.from(asHex, "hex"); if (asBytes.length != expectedLength) { - throw new errors.ErrBadPEM(`incorrect key length: expected ${expectedLength}, found ${asBytes.length}`); + throw new ErrBadPEM(`incorrect key length: expected ${expectedLength}, found ${asBytes.length}`); } buffers.push(asBytes); @@ -49,7 +49,7 @@ export function parse(text: string, expectedLength: number): Buffer[] { } if (linesAccumulator.length != 0) { - throw new errors.ErrBadPEM("incorrect file structure"); + throw new ErrBadPEM("incorrect file structure"); } return buffers; diff --git a/src-wallet/signature.ts b/src-wallet/signature.ts new file mode 100644 index 000000000..ec10ea536 --- /dev/null +++ b/src-wallet/signature.ts @@ -0,0 +1,14 @@ +/** + * Signature, as an immutable object. + */ +export class Signature { + private readonly buffer: Buffer; + + constructor(buffer: Buffer) { + this.buffer = buffer; + } + + hex() { + return this.buffer.toString("hex"); + } +} diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 1bb44f5e1..8d43d51aa 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -1,9 +1,7 @@ import * as tweetnacl from "tweetnacl"; -import { Address } from "../address"; -import { guardLength } from "../utils"; +import { Address } from "./address"; +import { guardLength } from "./utils"; import { parseUserKey } from "./pem"; -import {SignableMessage} from "../signableMessage"; -import {Logger} from "../logger"; export const USER_SEED_LENGTH = 32; export const USER_PUBKEY_LENGTH = 32; @@ -68,7 +66,7 @@ export class UserPublicKey { const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.buffer); return unsignedMessage != null; } catch (err) { - Logger.error(err); + console.error(err); return false; } } diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index be0b30e84..1370ca5e0 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -1,9 +1,9 @@ -import * as errors from "../errors"; -import { Address } from "../address"; -import { ISignable, ISigner } from "../interface"; -import { Signature } from "../signature"; +import { Address } from "./address"; +import { ISignable, ISigner } from "./interface"; +import { Signature } from "./signature"; import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; +import { ErrSignerCannotSign } from "./errors"; /** * ed25519 signer @@ -32,8 +32,8 @@ export class UserSigner implements ISigner { async sign(signable: ISignable): Promise { try { this.trySign(signable); - } catch (err) { - throw new errors.ErrSignerCannotSign(err); + } catch (err: any) { + throw new ErrSignerCannotSign(err); } } diff --git a/src-wallet/userVerifier.ts b/src-wallet/userVerifier.ts index d39bfa885..050b53ef9 100644 --- a/src-wallet/userVerifier.ts +++ b/src-wallet/userVerifier.ts @@ -1,6 +1,6 @@ -import {IVerifiable, IVerifier} from "../interface"; -import {Address} from "../address"; -import {UserPublicKey} from "./userKeys"; +import { IVerifiable, IVerifier } from "./interface"; +import { Address } from "./address"; +import { UserPublicKey } from "./userKeys"; /** * ed25519 signature verification diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index c0f7064d8..63100a9e2 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -1,6 +1,6 @@ import { UserPublicKey, UserSecretKey } from "./userKeys"; -import { EncryptedData, Encryptor, Decryptor, CipherAlgorithm, Version, KeyDerivationFunction, Randomness } from "../crypto"; -import {ScryptKeyDerivationParams} from "../crypto/derivationParams"; +import { EncryptedData, Encryptor, Decryptor, CipherAlgorithm, Version, KeyDerivationFunction, Randomness } from "./crypto"; +import { ScryptKeyDerivationParams } from "./crypto/derivationParams"; export class UserWallet { private readonly publicKey: UserPublicKey; diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 27b64670c..04ba8239d 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -1,4 +1,3 @@ -import * as errors from "../errors"; import { assert } from "chai"; import { loadMnemonic, loadPassword, loadTestWallets, TestWallet } from "../testutils"; import { UserSecretKey } from "./userKeys"; @@ -14,6 +13,7 @@ import { ChainID, GasLimit, GasPrice } from "../networkParams"; import { TransactionPayload } from "../transactionPayload"; import { UserVerifier } from "./userVerifier"; import { SignableMessage } from "../signableMessage"; +import { ErrInvariantFailed } from "./errors"; describe("test user wallets", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; @@ -63,8 +63,8 @@ describe("test user wallets", () => { }); it("should throw error when invalid input", () => { - assert.throw(() => new UserSecretKey(Buffer.alloc(42)), errors.ErrInvariantFailed); - assert.throw(() => UserSecretKey.fromString("foobar"), errors.ErrInvariantFailed); + assert.throw(() => new UserSecretKey(Buffer.alloc(42)), ErrInvariantFailed); + assert.throw(() => UserSecretKey.fromString("foobar"), ErrInvariantFailed); }); it("should handle PEM files", () => { diff --git a/src-wallet/utils.ts b/src-wallet/utils.ts new file mode 100644 index 000000000..80602639c --- /dev/null +++ b/src-wallet/utils.ts @@ -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}`); + } +} diff --git a/src-wallet/validatorKeys.ts b/src-wallet/validatorKeys.ts index 8b1ee75b0..c077bdd2d 100644 --- a/src-wallet/validatorKeys.ts +++ b/src-wallet/validatorKeys.ts @@ -1,5 +1,5 @@ -import * as errors from "../errors"; -import { guardLength } from "../utils"; +import { guardLength } from "./utils"; +import { ErrInvariantFailed } from "./errors"; import { parseValidatorKey } from "./pem"; const bls = require('@elrondnetwork/bls-wasm'); @@ -22,7 +22,7 @@ export class BLS { static guardInitialized() { if (!BLS.isInitialized) { - throw new errors.ErrInvariantFailed("BLS modules are not initalized. Make sure that 'await BLS.initIfNecessary()' is called correctly."); + throw new ErrInvariantFailed("BLS modules are not initalized. Make sure that 'await BLS.initIfNecessary()' is called correctly."); } } } diff --git a/src-wallet/validatorSigner.ts b/src-wallet/validatorSigner.ts index 887587f57..501f8892f 100644 --- a/src-wallet/validatorSigner.ts +++ b/src-wallet/validatorSigner.ts @@ -1,4 +1,4 @@ -import * as errors from "../errors"; +import { ErrSignerCannotSign } from "./errors"; import { BLS, ValidatorSecretKey } from "./validatorKeys"; /** @@ -15,7 +15,7 @@ export class ValidatorSigner { let secretKey = ValidatorSecretKey.fromPem(pemText, pemIndex); secretKey.sign(signable); } catch (err) { - throw new errors.ErrSignerCannotSign(err); + throw new ErrSignerCannotSign(err); } } } From dc389ca57557e5ea7d7087fab828c5628657c51b Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 23 Mar 2022 20:41:35 +0200 Subject: [PATCH 034/118] Fix tests upon extraction. Implement dummy test doubles. --- src-wallet/errors.ts | 9 +++ src-wallet/interface.ts | 2 + src-wallet/pem.spec.ts | 7 +- src-wallet/testdata/alice.json | 22 +++++++ src-wallet/testdata/alice.pem | 5 ++ src-wallet/testdata/bob.json | 22 +++++++ src-wallet/testdata/bob.pem | 5 ++ src-wallet/testdata/carol.json | 22 +++++++ src-wallet/testdata/carol.pem | 5 ++ src-wallet/testutils/files.ts | 28 ++++++++ src-wallet/testutils/message.ts | 33 ++++++++++ src-wallet/testutils/signature.ts | 13 ++++ src-wallet/testutils/transaction.ts | 55 ++++++++++++++++ src-wallet/testutils/wallets.ts | 35 ++++++++++ src-wallet/{address.ts => userAddress.ts} | 24 ++++++- src-wallet/userKeys.ts | 7 +- src-wallet/userSigner.ts | 5 +- src-wallet/userVerifier.ts | 5 +- src-wallet/userWallet.ts | 2 +- src-wallet/users.spec.ts | 80 +++++++++++------------ src-wallet/validatorSigner.ts | 2 +- 21 files changed, 331 insertions(+), 57 deletions(-) create mode 100644 src-wallet/testdata/alice.json create mode 100644 src-wallet/testdata/alice.pem create mode 100644 src-wallet/testdata/bob.json create mode 100644 src-wallet/testdata/bob.pem create mode 100644 src-wallet/testdata/carol.json create mode 100644 src-wallet/testdata/carol.pem create mode 100644 src-wallet/testutils/files.ts create mode 100644 src-wallet/testutils/message.ts create mode 100644 src-wallet/testutils/signature.ts create mode 100644 src-wallet/testutils/transaction.ts create mode 100644 src-wallet/testutils/wallets.ts rename src-wallet/{address.ts => userAddress.ts} (69%) diff --git a/src-wallet/errors.ts b/src-wallet/errors.ts index e27b36bc9..38e092196 100644 --- a/src-wallet/errors.ts +++ b/src-wallet/errors.ts @@ -54,3 +54,12 @@ export class ErrSignerCannotSign extends Err { super(`Cannot sign`, inner); } } + +/** + * Signals a bad user address. + */ + export class ErrBadAddress extends Err { + public constructor(value: string, inner?: Error) { + super(`Bad address: ${value}`, inner); + } +} diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts index a721048c7..b76da53c7 100644 --- a/src-wallet/interface.ts +++ b/src-wallet/interface.ts @@ -1,6 +1,7 @@ export interface IAddress { hex(): string; bech32(): string; + pubkey(): Buffer; } export interface ISignature { @@ -51,6 +52,7 @@ export interface IVerifiable { /** * Returns the signature that should be verified */ + getSignature(): ISignature; /** * Returns the signable object in its raw form - a sequence of bytes to be verified. diff --git a/src-wallet/pem.spec.ts b/src-wallet/pem.spec.ts index 2b6a7e74d..4861b230b 100644 --- a/src-wallet/pem.spec.ts +++ b/src-wallet/pem.spec.ts @@ -1,14 +1,17 @@ import { assert } from "chai"; -import { loadTestWallets, TestWallet } from "../testutils"; import { parse, parseUserKey, parseValidatorKey } from "./pem"; import { Buffer } from "buffer"; import { BLS } from "./validatorKeys"; import { ErrBadPEM } from "./errors"; +import { loadTestWallet, TestWallet } from "./testutils/wallets"; describe("test PEMs", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; + before(async function () { - ({ alice, bob, carol } = await loadTestWallets()); + alice = await loadTestWallet("alice"); + bob = await loadTestWallet("bob"); + carol = await loadTestWallet("carol"); }); it("should parseUserKey", () => { diff --git a/src-wallet/testdata/alice.json b/src-wallet/testdata/alice.json new file mode 100644 index 000000000..9e83170cf --- /dev/null +++ b/src-wallet/testdata/alice.json @@ -0,0 +1,22 @@ +{ + "version": 4, + "id": "0dc10c02-b59b-4bac-9710-6b2cfa4284ba", + "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "crypto": { + "ciphertext": "4c41ef6fdfd52c39b1585a875eb3c86d30a315642d0e35bb8205b6372c1882f135441099b11ff76345a6f3a930b5665aaf9f7325a32c8ccd60081c797aa2d538", + "cipherparams": { + "iv": "033182afaa1ebaafcde9ccc68a5eac31" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "4903bd0e7880baa04fc4f886518ac5c672cdc745a6bd13dcec2b6c12e9bffe8d", + "n": 4096, + "r": 8, + "p": 1 + }, + "mac": "5b4a6f14ab74ba7ca23db6847e28447f0e6a7724ba9664cf425df707a84f5a8b" + } +} diff --git a/src-wallet/testdata/alice.pem b/src-wallet/testdata/alice.pem new file mode 100644 index 000000000..d27bb68b4 --- /dev/null +++ b/src-wallet/testdata/alice.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- +NDEzZjQyNTc1ZjdmMjZmYWQzMzE3YTc3ODc3MTIxMmZkYjgwMjQ1ODUwOTgxZTQ4 +YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1ZDQy +MWYyNGMyOTE4MWU2Mzg4ODIyOGRjODFjYTYwZDY5ZTE= +-----END PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- \ No newline at end of file diff --git a/src-wallet/testdata/bob.json b/src-wallet/testdata/bob.json new file mode 100644 index 000000000..439b394a5 --- /dev/null +++ b/src-wallet/testdata/bob.json @@ -0,0 +1,22 @@ +{ + "version": 4, + "id": "85fdc8a7-7119-479d-b7fb-ab4413ed038d", + "address": "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", + "bech32": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + "crypto": { + "ciphertext": "c2664a31350aaf6a00525560db75c254d0aea65dc466441356c1dd59253cceb9e83eb05730ef3f42a11573c9a0e33dd952d488f00535b35357bb41d127b1eb82", + "cipherparams": { + "iv": "18378411e31f6c4e99f1435d9ab82831" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "18304455ac2dbe2a2018bda162bd03ef95b81622e99d8275c34a6d5e6932a68b", + "n": 4096, + "r": 8, + "p": 1 + }, + "mac": "23756172195ac483fa29025dc331bc7aa2c139533922a8dc08642eb0a677541f" + } +} diff --git a/src-wallet/testdata/bob.pem b/src-wallet/testdata/bob.pem new file mode 100644 index 000000000..00b5bc4ef --- /dev/null +++ b/src-wallet/testdata/bob.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx----- +YjhjYTZmODIwM2ZiNGI1NDVhOGU4M2M1Mzg0ZGEwMzNjNDE1ZGIxNTViNTNmYjVi +OGViYTdmZjVhMDM5ZDYzOTgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAy +OWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg= +-----END PRIVATE KEY for erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx----- \ No newline at end of file diff --git a/src-wallet/testdata/carol.json b/src-wallet/testdata/carol.json new file mode 100644 index 000000000..3614a5ba2 --- /dev/null +++ b/src-wallet/testdata/carol.json @@ -0,0 +1,22 @@ +{ + "version": 4, + "id": "65894f35-d142-41d2-9335-6ad02e0ed0be", + "address": "b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", + "bech32": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + "crypto": { + "ciphertext": "bdfb984a1e7c7460f0a289749609730cdc99d7ce85b59305417c2c0f007b2a6aaa7203dd94dbf27315bced39b0b281769fbc70b01e6e57f89ae2f2a9e9100007", + "cipherparams": { + "iv": "258ed2b4dc506b4dc9d274b0449b0eb0" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "4f2f5530ce28dc0210962589b908f52714f75c8fb79ff18bdd0024c43c7a220b", + "n": 4096, + "r": 8, + "p": 1 + }, + "mac": "f8de52e2627024eaa33f2ee5eadcd3d3815e10dd274ea966dc083d000cc8b258" + } +} diff --git a/src-wallet/testdata/carol.pem b/src-wallet/testdata/carol.pem new file mode 100644 index 000000000..5551c9c0e --- /dev/null +++ b/src-wallet/testdata/carol.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY for erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8----- +ZTI1M2E1NzFjYTE1M2RjMmFlZTg0NTgxOWY3NGJjYzk3NzNiMDU4NmVkZWFkMTVh +OTRjYjcyMzVhNTAyNzQzNmIyYTExNTU1Y2U1MjFlNDk0NGUwOWFiMTc1NDlkODVi +NDg3ZGNkMjZjODRiNTAxN2EzOWUzMWEzNjcwODg5YmE= +-----END PRIVATE KEY for erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8----- \ No newline at end of file diff --git a/src-wallet/testutils/files.ts b/src-wallet/testutils/files.ts new file mode 100644 index 000000000..09abdf1fe --- /dev/null +++ b/src-wallet/testutils/files.ts @@ -0,0 +1,28 @@ +import * as fs from "fs"; +import axios from "axios"; + +export async function readTestFile(filePath: string): Promise { + if (isOnBrowserTests()) { + return await downloadTextFile(filePath); + } + + return await fs.promises.readFile(filePath, { encoding: "utf8" }); +} + +export function isOnBrowserTests() { + const BROWSER_TESTS_URL = "browser-tests"; + + let noWindow = typeof window === "undefined"; + if (noWindow) { + return false; + } + + let isOnTests = window.location.href.includes(BROWSER_TESTS_URL); + return isOnTests; +} + +export async function downloadTextFile(url: string) { + let response = await axios.get(url, { responseType: "text", transformResponse: [] }); + let text = response.data.toString(); + return text; +} diff --git a/src-wallet/testutils/message.ts b/src-wallet/testutils/message.ts new file mode 100644 index 000000000..84bd3710c --- /dev/null +++ b/src-wallet/testutils/message.ts @@ -0,0 +1,33 @@ +import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; +import { TestSignature } from "./signature"; + +/** + * A dummy message used in tests. + */ +export class TestMessage implements ISignable, IVerifiable { + foo: string = ""; + bar: string = ""; + signature: string = ""; + + constructor(init?: Partial) { + Object.assign(this, init); + } + + serializeForSigning(_signedBy: IAddress): Buffer { + let plainObject = { + foo: this.foo, + bar: this.bar + }; + + let serialized = JSON.stringify(plainObject); + return Buffer.from(serialized); + } + + applySignature(signature: ISignature, _signedBy: IAddress): void { + this.signature = signature.hex(); + } + + getSignature(): ISignature { + return new TestSignature(this.signature); + } +} diff --git a/src-wallet/testutils/signature.ts b/src-wallet/testutils/signature.ts new file mode 100644 index 000000000..0f9db1a23 --- /dev/null +++ b/src-wallet/testutils/signature.ts @@ -0,0 +1,13 @@ +import { ISignature } from "../interface"; + +export class TestSignature implements ISignature { + readonly value: string; + + constructor(value: string) { + this.value = value; + } + + hex(): string { + return this.value; + } +} diff --git a/src-wallet/testutils/transaction.ts b/src-wallet/testutils/transaction.ts new file mode 100644 index 000000000..4db892e3e --- /dev/null +++ b/src-wallet/testutils/transaction.ts @@ -0,0 +1,55 @@ +import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; +import { TestSignature } from "./signature"; + +/** + * A dummy transaction used in tests. + */ +export class TestTransaction implements ISignable, IVerifiable { + nonce: number = 0; + value: string = ""; + receiver: string = ""; + gasPrice: number = 0; + gasLimit: number = 0; + data: string = ""; + chainID: string = ""; + version: number = 1; + options: number = 0; + + sender: string = ""; + signature: string = ""; + + constructor(init?: Partial) { + Object.assign(this, init); + } + + serializeForSigning(signedBy: IAddress): Buffer { + let sender = signedBy.bech32(); + let dataEncoded = this.data ? Buffer.from(this.data).toString("base64") : undefined; + let options = this.options ? this.options : undefined; + + let plainObject = { + nonce: this.nonce, + value: this.value, + receiver: this.receiver, + sender: sender, + gasPrice: this.gasPrice, + gasLimit: this.gasLimit, + data: dataEncoded, + chainID: this.chainID, + version: this.version, + options: options + }; + + let serialized = JSON.stringify(plainObject); + return Buffer.from(serialized); + } + + applySignature(signature: ISignature, signedBy: IAddress): void { + this.sender = signedBy.bech32(); + this.signature = signature.hex(); + } + + getSignature(): ISignature { + return new TestSignature(this.signature); + } +} diff --git a/src-wallet/testutils/wallets.ts b/src-wallet/testutils/wallets.ts new file mode 100644 index 000000000..353c777d5 --- /dev/null +++ b/src-wallet/testutils/wallets.ts @@ -0,0 +1,35 @@ +import * as path from "path"; +import { UserAddress } from "../userAddress"; +import { UserSecretKey } from "../userKeys"; +import { readTestFile } from "./files"; + +export const DummyPassword = "password"; +export const DummyMnemonic = "moral volcano peasant pass circle pen over picture flat shop clap goat never lyrics gather prepare woman film husband gravity behind test tiger improve"; + +export async function loadTestWallet(name: string): Promise { + let testdataPath = path.resolve(__dirname, "..", "testdata"); + let jsonFilePath = path.resolve(testdataPath, `${name}.json`); + let pemFilePath = path.resolve(testdataPath, `${name}.pem`); + + let jsonWallet = JSON.parse(await readTestFile(jsonFilePath)); + let pemText = await readTestFile(pemFilePath); + let pemKey = UserSecretKey.fromPem(pemText); + let address = new UserAddress(Buffer.from(jsonWallet.address, "hex")); + return new TestWallet(address, pemKey.hex(), jsonWallet, pemText); +} + +export class TestWallet { + readonly address: UserAddress; + readonly secretKeyHex: string; + readonly secretKey: Buffer; + readonly keyFileObject: any; + readonly pemFileText: any; + + constructor(address: UserAddress, secretKeyHex: string, keyFileObject: any, pemFileText: any) { + this.address = address; + this.secretKeyHex = secretKeyHex; + this.secretKey = Buffer.from(secretKeyHex, "hex"); + this.keyFileObject = keyFileObject; + this.pemFileText = pemFileText; + } +} diff --git a/src-wallet/address.ts b/src-wallet/userAddress.ts similarity index 69% rename from src-wallet/address.ts rename to src-wallet/userAddress.ts index a31749c08..f87ae8722 100644 --- a/src-wallet/address.ts +++ b/src-wallet/userAddress.ts @@ -1,4 +1,5 @@ import * as bech32 from "bech32"; +import { ErrBadAddress } from "./errors"; /** * The human-readable-part of the bech32 addresses. @@ -6,15 +7,32 @@ import * as bech32 from "bech32"; const HRP = "erd"; /** - * An Elrond Address, as an immutable object. + * A user Address, as an immutable object. */ -export class Address { +export class UserAddress { private readonly buffer: Buffer; public constructor(buffer: Buffer) { this.buffer = buffer; } + static fromBech32(value: string): UserAddress { + let decoded; + + try { + decoded = bech32.decode(value); + } catch (err: any) { + throw new ErrBadAddress(value, err); + } + + if (decoded.prefix != HRP) { + throw new ErrBadAddress(value); + } + + let pubkey = Buffer.from(bech32.fromWords(decoded.words)); + return new UserAddress(pubkey); + } + /** * Returns the hex representation of the address (pubkey) */ @@ -48,7 +66,7 @@ export class Address { /** * Compares the address to another address */ - equals(other: Address): boolean { + equals(other: UserAddress): boolean { return this.buffer.compare(other.buffer) == 0; } diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 8d43d51aa..d18ddf49b 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -1,7 +1,8 @@ import * as tweetnacl from "tweetnacl"; -import { Address } from "./address"; +import { UserAddress } from "./userAddress"; import { guardLength } from "./utils"; import { parseUserKey } from "./pem"; +import { IAddress } from "./interface"; export const USER_SEED_LENGTH = 32; export const USER_PUBKEY_LENGTH = 32; @@ -75,8 +76,8 @@ export class UserPublicKey { return this.buffer.toString("hex"); } - toAddress(): Address { - return new Address(this.buffer); + toAddress(): IAddress { + return new UserAddress(this.buffer); } valueOf(): Buffer { diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 1370ca5e0..c7cdde4e0 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -1,5 +1,4 @@ -import { Address } from "./address"; -import { ISignable, ISigner } from "./interface"; +import { IAddress, ISignable, ISigner } from "./interface"; import { Signature } from "./signature"; import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; @@ -49,7 +48,7 @@ export class UserSigner implements ISigner { /** * Gets the address of the signer. */ - getAddress(): Address { + getAddress(): IAddress { return this.secretKey.generatePublicKey().toAddress(); } } diff --git a/src-wallet/userVerifier.ts b/src-wallet/userVerifier.ts index 050b53ef9..73e8e4f6b 100644 --- a/src-wallet/userVerifier.ts +++ b/src-wallet/userVerifier.ts @@ -1,5 +1,4 @@ -import { IVerifiable, IVerifier } from "./interface"; -import { Address } from "./address"; +import { IAddress, IVerifiable, IVerifier } from "./interface"; import { UserPublicKey } from "./userKeys"; /** @@ -12,7 +11,7 @@ export class UserVerifier implements IVerifier { this.publicKey = publicKey; } - static fromAddress(address: Address): IVerifier { + static fromAddress(address: IAddress): IVerifier { let publicKey = new UserPublicKey(address.pubkey()); return new UserVerifier(publicKey); } diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 63100a9e2..485e97a3c 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -34,7 +34,7 @@ export class UserWallet { * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { - const encryptedData = UserWallet.edFromJSON(keyFileObject) + const encryptedData = UserWallet.edFromJSON(keyFileObject); let text = Decryptor.decrypt(encryptedData, password); while (text.length < 32) { diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 04ba8239d..ef8c0af00 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -1,26 +1,24 @@ import { assert } from "chai"; -import { loadMnemonic, loadPassword, loadTestWallets, TestWallet } from "../testutils"; import { UserSecretKey } from "./userKeys"; import { Mnemonic } from "./mnemonic"; import { UserWallet } from "./userWallet"; -import { Randomness } from "../crypto"; -import { Address } from "../address"; +import { Randomness } from "./crypto"; +import { UserAddress } from "./userAddress"; import { UserSigner } from "./userSigner"; -import { Transaction } from "../transaction"; -import { Nonce } from "../nonce"; -import { Balance } from "../balance"; -import { ChainID, GasLimit, GasPrice } from "../networkParams"; -import { TransactionPayload } from "../transactionPayload"; import { UserVerifier } from "./userVerifier"; -import { SignableMessage } from "../signableMessage"; import { ErrInvariantFailed } from "./errors"; +import { TestMessage } from "./testutils/message"; +import { DummyMnemonic, DummyPassword, loadTestWallet, TestWallet } from "./testutils/wallets"; +import { TestTransaction } from "./testutils/transaction"; describe("test user wallets", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; - let password: string; + let password: string = DummyPassword; + before(async function () { - ({ alice, bob, carol } = await loadTestWallets()); - password = await loadPassword(); + alice = await loadTestWallet("alice"); + bob = await loadTestWallet("bob"); + carol = await loadTestWallet("carol"); }); it("should generate mnemonic", () => { @@ -30,7 +28,7 @@ describe("test user wallets", () => { }); it("should derive keys", async () => { - let mnemonic = Mnemonic.fromString(await loadMnemonic()); + let mnemonic = Mnemonic.fromString(DummyMnemonic); assert.equal(mnemonic.deriveKey(0).hex(), alice.secretKeyHex); assert.equal(mnemonic.deriveKey(1).hex(), bob.secretKeyHex); @@ -124,18 +122,17 @@ describe("test user wallets", () => { it("should sign transactions", async () => { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); - let sender = new Address("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); - let receiver = new Address("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"); - + let sender = UserAddress.fromBech32("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); + // With data field - let transaction = new Transaction({ - nonce: new Nonce(0), - value: Balance.Zero(), - receiver: receiver, - gasPrice: new GasPrice(1000000000), - gasLimit: new GasLimit(50000), - data: new TransactionPayload("foo"), - chainID: new ChainID("1") + let transaction = new TestTransaction({ + nonce: 0, + value: "0", + receiver: "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + gasPrice: 1000000000, + gasLimit: 50000, + data: "foo", + chainID: "1" }); let serialized = transaction.serializeForSigning(sender).toString(); @@ -145,13 +142,13 @@ describe("test user wallets", () => { assert.equal(transaction.getSignature().hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); assert.isTrue(verifier.verify(transaction)); // Without data field - transaction = new Transaction({ - nonce: new Nonce(8), - value: Balance.fromString("10000000000000000000"), - receiver: receiver, - gasPrice: new GasPrice(1000000000), - gasLimit: new GasLimit(50000), - chainID: new ChainID("1") + transaction = new TestTransaction({ + nonce: 8, + value: "10000000000000000000", + receiver: "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + gasPrice: 1000000000, + gasLimit: 50000, + chainID: "1" }); serialized = transaction.serializeForSigning(sender).toString(); @@ -164,14 +161,14 @@ describe("test user wallets", () => { it("should sign transactions using PEM files", async () => { let signer = UserSigner.fromPem(alice.pemFileText); - let transaction = new Transaction({ - nonce: new Nonce(0), - value: Balance.Zero(), - receiver: new Address("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"), - gasPrice: new GasPrice(1000000000), - gasLimit: new GasLimit(50000), - data: new TransactionPayload("foo"), - chainID: new ChainID("1") + let transaction = new TestTransaction({ + nonce: 0, + value: "0", + receiver: "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + gasPrice: 1000000000, + gasLimit: 50000, + data: "foo", + chainID: "1" }); await signer.sign(transaction); @@ -181,8 +178,9 @@ describe("test user wallets", () => { it("signs a general message", function () { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); - const message = new SignableMessage({ - message: Buffer.from("hello world") + const message = new TestMessage({ + foo: "hello", + bar: "world" }); signer.sign(message); diff --git a/src-wallet/validatorSigner.ts b/src-wallet/validatorSigner.ts index 501f8892f..92e4e2249 100644 --- a/src-wallet/validatorSigner.ts +++ b/src-wallet/validatorSigner.ts @@ -14,7 +14,7 @@ export class ValidatorSigner { try { let secretKey = ValidatorSecretKey.fromPem(pemText, pemIndex); secretKey.sign(signable); - } catch (err) { + } catch (err: any) { throw new ErrSignerCannotSign(err); } } From a24eeea480fb034e5d709750959fde0f1b0eee96 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 23 Mar 2022 21:26:49 +0200 Subject: [PATCH 035/118] Fix browser tests. --- src-wallet/users.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index ef8c0af00..ee71c03e5 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -49,15 +49,15 @@ describe("test user wallets", () => { secretKey = new UserSecretKey(Buffer.from(alice.secretKeyHex, "hex")); assert.equal(secretKey.generatePublicKey().hex(), alice.address.hex()); - assert.isTrue(secretKey.generatePublicKey().toAddress().equals(alice.address)); + assert.deepEqual(secretKey.generatePublicKey().toAddress(), alice.address); secretKey = new UserSecretKey(Buffer.from(bob.secretKeyHex, "hex")); assert.equal(secretKey.generatePublicKey().hex(), bob.address.hex()); - assert.isTrue(secretKey.generatePublicKey().toAddress().equals(bob.address)); + assert.deepEqual(secretKey.generatePublicKey().toAddress(), bob.address); secretKey = new UserSecretKey(Buffer.from(carol.secretKeyHex, "hex")); assert.equal(secretKey.generatePublicKey().hex(), carol.address.hex()); - assert.isTrue(secretKey.generatePublicKey().toAddress().equals(carol.address)); + assert.deepEqual(secretKey.generatePublicKey().toAddress(), carol.address); }); it("should throw error when invalid input", () => { From cdc7b74a629189b3cf0aca832502c81b8a2c837e Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Wed, 23 Mar 2022 21:43:59 +0200 Subject: [PATCH 036/118] Fix after self-review. --- src-wallet/errors.ts | 4 ++-- src-wallet/interface.ts | 2 +- src-wallet/testutils/message.ts | 4 ++-- src-wallet/testutils/signature.ts | 13 ------------- src-wallet/testutils/transaction.ts | 4 ++-- src-wallet/userAddress.ts | 14 -------------- 6 files changed, 7 insertions(+), 34 deletions(-) delete mode 100644 src-wallet/testutils/signature.ts diff --git a/src-wallet/errors.ts b/src-wallet/errors.ts index 38e092196..68b49a8dd 100644 --- a/src-wallet/errors.ts +++ b/src-wallet/errors.ts @@ -1,5 +1,5 @@ /** - * The base class for `erdjs` exceptions (errors). + * The base class for exceptions (errors). */ export class Err extends Error { inner: Error | undefined = undefined; @@ -56,7 +56,7 @@ export class ErrSignerCannotSign extends Err { } /** - * Signals a bad user address. + * Signals a bad address. */ export class ErrBadAddress extends Err { public constructor(value: string, inner?: Error) { diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts index b76da53c7..04c4fdee3 100644 --- a/src-wallet/interface.ts +++ b/src-wallet/interface.ts @@ -52,8 +52,8 @@ export interface IVerifiable { /** * Returns the signature that should be verified */ - getSignature(): ISignature; + /** * Returns the signable object in its raw form - a sequence of bytes to be verified. */ diff --git a/src-wallet/testutils/message.ts b/src-wallet/testutils/message.ts index 84bd3710c..1fe2f542b 100644 --- a/src-wallet/testutils/message.ts +++ b/src-wallet/testutils/message.ts @@ -1,5 +1,5 @@ import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; -import { TestSignature } from "./signature"; +import { Signature } from "../signature"; /** * A dummy message used in tests. @@ -28,6 +28,6 @@ export class TestMessage implements ISignable, IVerifiable { } getSignature(): ISignature { - return new TestSignature(this.signature); + return new Signature(Buffer.from(this.signature, "hex")); } } diff --git a/src-wallet/testutils/signature.ts b/src-wallet/testutils/signature.ts deleted file mode 100644 index 0f9db1a23..000000000 --- a/src-wallet/testutils/signature.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ISignature } from "../interface"; - -export class TestSignature implements ISignature { - readonly value: string; - - constructor(value: string) { - this.value = value; - } - - hex(): string { - return this.value; - } -} diff --git a/src-wallet/testutils/transaction.ts b/src-wallet/testutils/transaction.ts index 4db892e3e..bd0d04c06 100644 --- a/src-wallet/testutils/transaction.ts +++ b/src-wallet/testutils/transaction.ts @@ -1,5 +1,5 @@ import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; -import { TestSignature } from "./signature"; +import { Signature } from "../signature"; /** * A dummy transaction used in tests. @@ -50,6 +50,6 @@ export class TestTransaction implements ISignable, IVerifiable { } getSignature(): ISignature { - return new TestSignature(this.signature); + return new Signature(Buffer.from(this.signature, "hex")); } } diff --git a/src-wallet/userAddress.ts b/src-wallet/userAddress.ts index f87ae8722..3ed7efe8f 100644 --- a/src-wallet/userAddress.ts +++ b/src-wallet/userAddress.ts @@ -56,20 +56,6 @@ export class UserAddress { return this.buffer; } - /** - * Returns whether the address is empty. - */ - isEmpty() { - return !this.buffer; - } - - /** - * Compares the address to another address - */ - equals(other: UserAddress): boolean { - return this.buffer.compare(other.buffer) == 0; - } - /** * Returns the bech32 representation of the address */ From 336a46c207b193bae0da01e5da07987ea318f837 Mon Sep 17 00:00:00 2001 From: Andrei Bancioiu Date: Fri, 25 Mar 2022 10:34:23 +0200 Subject: [PATCH 037/118] Fix after review. --- src-wallet/{utils.ts => assertions.ts} | 0 src-wallet/crypto/decryptor.ts | 4 ++-- src-wallet/errors.ts | 9 --------- src-wallet/userKeys.ts | 2 +- src-wallet/validatorKeys.ts | 2 +- 5 files changed, 4 insertions(+), 13 deletions(-) rename src-wallet/{utils.ts => assertions.ts} (100%) diff --git a/src-wallet/utils.ts b/src-wallet/assertions.ts similarity index 100% rename from src-wallet/utils.ts rename to src-wallet/assertions.ts diff --git a/src-wallet/crypto/decryptor.ts b/src-wallet/crypto/decryptor.ts index 6a859d1d3..7d6dbac90 100644 --- a/src-wallet/crypto/decryptor.ts +++ b/src-wallet/crypto/decryptor.ts @@ -1,7 +1,7 @@ import crypto from "crypto"; import { EncryptedData } from "./encryptedData"; import { DigestAlgorithm } from "./constants"; -import { ErrWrongPassword } from "../errors"; +import { Err } from "../errors"; export class Decryptor { public static decrypt(data: EncryptedData, password: string): Buffer { @@ -17,7 +17,7 @@ export class Decryptor { const actualMAC = data.mac; if (computedMAC.toString("hex") !== actualMAC) { - throw new ErrWrongPassword("MAC mismatch"); + throw new Err("MAC mismatch, possibly wrong password"); } const decipher = crypto.createDecipheriv(data.cipher, derivedKeyFirstHalf, iv); diff --git a/src-wallet/errors.ts b/src-wallet/errors.ts index 68b49a8dd..19f1ac852 100644 --- a/src-wallet/errors.ts +++ b/src-wallet/errors.ts @@ -28,15 +28,6 @@ export class ErrWrongMnemonic extends Err { } } -/** - * Signals a wrong password. - */ - export class ErrWrongPassword extends Err { - public constructor(message: string) { - super(`Possibly wrong password: ${message}`); - } -} - /** * Signals a bad PEM file. */ diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index d18ddf49b..eab6b92f2 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -1,6 +1,6 @@ import * as tweetnacl from "tweetnacl"; import { UserAddress } from "./userAddress"; -import { guardLength } from "./utils"; +import { guardLength } from "./assertions"; import { parseUserKey } from "./pem"; import { IAddress } from "./interface"; diff --git a/src-wallet/validatorKeys.ts b/src-wallet/validatorKeys.ts index c077bdd2d..441c4ac07 100644 --- a/src-wallet/validatorKeys.ts +++ b/src-wallet/validatorKeys.ts @@ -1,4 +1,4 @@ -import { guardLength } from "./utils"; +import { guardLength } from "./assertions"; import { ErrInvariantFailed } from "./errors"; import { parseValidatorKey } from "./pem"; From 651277a271aca3376c2bdccd9002a29f5d2fe488 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Thu, 11 Aug 2022 17:14:16 +0300 Subject: [PATCH 038/118] encryptor partial implementation --- src-wallet/crypto/pubkeyEncryptor.ts | 32 ++++++++++++++++++++ src-wallet/crypto/x25519EncryptedData.ts | 37 ++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src-wallet/crypto/pubkeyEncryptor.ts create mode 100644 src-wallet/crypto/x25519EncryptedData.ts diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src-wallet/crypto/pubkeyEncryptor.ts new file mode 100644 index 000000000..2c5bc5ecc --- /dev/null +++ b/src-wallet/crypto/pubkeyEncryptor.ts @@ -0,0 +1,32 @@ +import nacl from "tweetnacl"; +import ed2curve from "ed2curve"; +import {X25519EncryptedData} from "./x25519EncryptedData"; +import {UserPublicKey, UserSecretKey} from "../userKeys"; + +export class PubkeyEncryptor { + public static encrypt(data: Buffer, endUserPubKey: UserPublicKey, authSecretKey: UserSecretKey): X25519EncryptedData { + // create a new x225519 keypair that will be used for EDH + const edhPair = nacl.sign.keyPair(); + + const endUserDHPubKey = ed2curve.convertPublicKey(endUserPubKey.valueOf()); + if (endUserDHPubKey === null) { + throw new Error("Could not convert ed25519 public key to x25519"); + } + + const nonce = nacl.randomBytes(24); + const encryptedMessage = nacl.box(data, nonce, endUserDHPubKey, edhPair.secretKey); + + // Note that the ciphertext is already authenticated for the ephemeral key - but we want it authenticated by + // the an elrond ed25519 key which the user interacts with. A signature over H(ciphertext | edhPubKey) + // would be enough + + + return new X25519EncryptedData({ + version: 1, + nonce: Buffer.from(nonce).toString('hex'), + cipher: "", + ciphertext: Buffer.from(encryptedMessage).toString('hex'), + mac: '', + }); + } +} \ No newline at end of file diff --git a/src-wallet/crypto/x25519EncryptedData.ts b/src-wallet/crypto/x25519EncryptedData.ts new file mode 100644 index 000000000..3c16e61a2 --- /dev/null +++ b/src-wallet/crypto/x25519EncryptedData.ts @@ -0,0 +1,37 @@ +export class X25519EncryptedData { + nonce: string; + version: number; + cipher: string; + ciphertext: string; + mac: string; + + constructor(data: Omit) { + this.nonce = data.nonce; + this.version = data.version; + this.cipher = data.cipher; + this.ciphertext = data.ciphertext; + this.mac = data.mac; + } + + toJSON(): any { + return { + version: this.version, + nonce: this.nonce, + crypto: { + ciphertext: this.ciphertext, + cipher: this.cipher, + mac: this.mac, + } + }; + } + + static fromJSON(data: any): X25519EncryptedData { + return new X25519EncryptedData({ + nonce: data.nonce, + version: data.version, + ciphertext: data.crypto.ciphertext, + cipher: data.crypto.cipher, + mac: data.crypto.mac, + }); + } +} \ No newline at end of file From bc5089a1ae1ab63e31a3af56032f8f4faff149b5 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Fri, 12 Aug 2022 15:16:31 +0300 Subject: [PATCH 039/118] finished encryptor --- src-wallet/crypto/constants.ts | 4 ++++ src-wallet/crypto/pubkeyEncryptor.ts | 28 +++++++++++++++++------- src-wallet/crypto/x25519EncryptedData.ts | 8 +++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src-wallet/crypto/constants.ts b/src-wallet/crypto/constants.ts index a9fe61a54..a06ffe2cc 100644 --- a/src-wallet/crypto/constants.ts +++ b/src-wallet/crypto/constants.ts @@ -3,3 +3,7 @@ export const Version = 4; export const CipherAlgorithm = "aes-128-ctr"; export const DigestAlgorithm = "sha256"; export const KeyDerivationFunction = "scrypt"; + +// X25519 public key encryption +export const PubKeyEncVersion = 1; +export const PubKeyEncCipher = "x25519-xsalsa20-poly1305"; diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src-wallet/crypto/pubkeyEncryptor.ts index 2c5bc5ecc..78dfd8ad7 100644 --- a/src-wallet/crypto/pubkeyEncryptor.ts +++ b/src-wallet/crypto/pubkeyEncryptor.ts @@ -1,32 +1,44 @@ import nacl from "tweetnacl"; import ed2curve from "ed2curve"; +import crypto, {sign} from "crypto"; import {X25519EncryptedData} from "./x25519EncryptedData"; import {UserPublicKey, UserSecretKey} from "../userKeys"; +import {PubKeyEncCipher, PubKeyEncVersion} from "./constants"; +import {UserSigner} from "../userSigner"; export class PubkeyEncryptor { - public static encrypt(data: Buffer, endUserPubKey: UserPublicKey, authSecretKey: UserSecretKey): X25519EncryptedData { + public static encrypt(data: Buffer, recipientPubKey: UserPublicKey, authSecretKey: UserSecretKey): X25519EncryptedData { // create a new x225519 keypair that will be used for EDH const edhPair = nacl.sign.keyPair(); - const endUserDHPubKey = ed2curve.convertPublicKey(endUserPubKey.valueOf()); - if (endUserDHPubKey === null) { + const recipientDHPubKey = ed2curve.convertPublicKey(recipientPubKey.valueOf()); + if (recipientDHPubKey === null) { throw new Error("Could not convert ed25519 public key to x25519"); } const nonce = nacl.randomBytes(24); - const encryptedMessage = nacl.box(data, nonce, endUserDHPubKey, edhPair.secretKey); + const encryptedMessage = nacl.box(data, nonce, recipientDHPubKey, edhPair.secretKey); // Note that the ciphertext is already authenticated for the ephemeral key - but we want it authenticated by // the an elrond ed25519 key which the user interacts with. A signature over H(ciphertext | edhPubKey) // would be enough + const authSig = crypto.createHash('sha256').update( + Buffer.concat([encryptedMessage, edhPair.publicKey]) + ).digest(); + const signature = authSecretKey.sign(authSig); return new X25519EncryptedData({ - version: 1, + version: PubKeyEncVersion, nonce: Buffer.from(nonce).toString('hex'), - cipher: "", + cipher: PubKeyEncCipher, ciphertext: Buffer.from(encryptedMessage).toString('hex'), - mac: '', + mac: signature.toString('hex'), + identities: { + recipient: recipientPubKey.hex(), + ephemeralPubKey: Buffer.from(edhPair.publicKey).toString('hex'), + originatorPubKey: authSecretKey.generatePublicKey().hex(), + } }); } -} \ No newline at end of file +} diff --git a/src-wallet/crypto/x25519EncryptedData.ts b/src-wallet/crypto/x25519EncryptedData.ts index 3c16e61a2..21ff2cfdb 100644 --- a/src-wallet/crypto/x25519EncryptedData.ts +++ b/src-wallet/crypto/x25519EncryptedData.ts @@ -4,6 +4,11 @@ export class X25519EncryptedData { cipher: string; ciphertext: string; mac: string; + identities: { + recipient: string, + ephemeralPubKey: string, + originatorPubKey: string, + }; constructor(data: Omit) { this.nonce = data.nonce; @@ -11,12 +16,14 @@ export class X25519EncryptedData { this.cipher = data.cipher; this.ciphertext = data.ciphertext; this.mac = data.mac; + this.identities = data.identities; } toJSON(): any { return { version: this.version, nonce: this.nonce, + identities: this.identities, crypto: { ciphertext: this.ciphertext, cipher: this.cipher, @@ -32,6 +39,7 @@ export class X25519EncryptedData { ciphertext: data.crypto.ciphertext, cipher: data.crypto.cipher, mac: data.crypto.mac, + identities: data.identities, }); } } \ No newline at end of file From 04b0d01e282ad85fa6620dedf79fef3eeb788cf3 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Tue, 16 Aug 2022 13:41:57 +0300 Subject: [PATCH 040/118] add decryptor and basic unit tests --- src-wallet/crypto/constants.ts | 1 + src-wallet/crypto/decryptor.ts | 2 +- src-wallet/crypto/encryptor.ts | 2 +- src-wallet/crypto/pubkeyDecryptor.ts | 36 +++++++++++++++++++++++++ src-wallet/crypto/pubkeyEncrypt.spec.ts | 35 ++++++++++++++++++++++++ src-wallet/crypto/pubkeyEncryptor.ts | 11 ++++---- 6 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 src-wallet/crypto/pubkeyDecryptor.ts create mode 100644 src-wallet/crypto/pubkeyEncrypt.spec.ts diff --git a/src-wallet/crypto/constants.ts b/src-wallet/crypto/constants.ts index a06ffe2cc..07a4ca7c4 100644 --- a/src-wallet/crypto/constants.ts +++ b/src-wallet/crypto/constants.ts @@ -6,4 +6,5 @@ export const KeyDerivationFunction = "scrypt"; // X25519 public key encryption export const PubKeyEncVersion = 1; +export const PubKeyEncNonceLength = 24; export const PubKeyEncCipher = "x25519-xsalsa20-poly1305"; diff --git a/src-wallet/crypto/decryptor.ts b/src-wallet/crypto/decryptor.ts index 7d6dbac90..0a42be1b0 100644 --- a/src-wallet/crypto/decryptor.ts +++ b/src-wallet/crypto/decryptor.ts @@ -4,7 +4,7 @@ import { DigestAlgorithm } from "./constants"; import { Err } from "../errors"; export class Decryptor { - public static decrypt(data: EncryptedData, password: string): Buffer { + 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"); diff --git a/src-wallet/crypto/encryptor.ts b/src-wallet/crypto/encryptor.ts index e329dbb9c..33379cbc3 100644 --- a/src-wallet/crypto/encryptor.ts +++ b/src-wallet/crypto/encryptor.ts @@ -5,7 +5,7 @@ import { CipherAlgorithm, DigestAlgorithm, Version, KeyDerivationFunction } from import {EncryptedData} from "./encryptedData"; export class Encryptor { - public static encrypt(data: Buffer, password: string, randomness: Randomness = new Randomness()): EncryptedData { + static encrypt(data: Buffer, password: string, randomness: Randomness = new Randomness()): EncryptedData { const kdParams = new ScryptKeyDerivationParams(); const derivedKey = kdParams.generateDerivedKey(Buffer.from(password), randomness.salt); const derivedKeyFirstHalf = derivedKey.slice(0, 16); diff --git a/src-wallet/crypto/pubkeyDecryptor.ts b/src-wallet/crypto/pubkeyDecryptor.ts new file mode 100644 index 000000000..9806fcfe0 --- /dev/null +++ b/src-wallet/crypto/pubkeyDecryptor.ts @@ -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); + } +} diff --git a/src-wallet/crypto/pubkeyEncrypt.spec.ts b/src-wallet/crypto/pubkeyEncrypt.spec.ts new file mode 100644 index 000000000..53c9cf345 --- /dev/null +++ b/src-wallet/crypto/pubkeyEncrypt.spec.ts @@ -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("my secret text for x"); + let encryptedData: X25519EncryptedData; + + before(async () => { + alice = await loadTestWallet("alice"); + bob = await loadTestWallet("bob"); + carol = await loadTestWallet("carol"); + + encryptedData = PubkeyEncryptor.encrypt(sensitiveData, new UserPublicKey(bob.address.pubkey()), new UserSecretKey(alice.secretKey)); + }); + + it("encrypts/decrypts", () => { + const decryptedData = PubkeyDecryptor.decrypt(encryptedData, new UserSecretKey(bob.secretKey)); + assert.equal(sensitiveData.toString('hex'), decryptedData.toString('hex')); + }); + + it("fails for different originator", () => { + encryptedData.identities.originatorPubKey = carol.address.hex(); + assert.throws(() => PubkeyDecryptor.decrypt(encryptedData, new UserSecretKey(bob.secretKey)), "Invalid authentication for encrypted message originator"); + }); + + it("fails for different DH public key", () => { + encryptedData.identities.ephemeralPubKey = carol.address.hex(); + assert.throws(() => PubkeyDecryptor.decrypt(encryptedData, new UserSecretKey(bob.secretKey)), "Invalid authentication for encrypted message originator"); + }); +}); diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src-wallet/crypto/pubkeyEncryptor.ts index 78dfd8ad7..2dead968d 100644 --- a/src-wallet/crypto/pubkeyEncryptor.ts +++ b/src-wallet/crypto/pubkeyEncryptor.ts @@ -3,21 +3,20 @@ import ed2curve from "ed2curve"; import crypto, {sign} from "crypto"; import {X25519EncryptedData} from "./x25519EncryptedData"; import {UserPublicKey, UserSecretKey} from "../userKeys"; -import {PubKeyEncCipher, PubKeyEncVersion} from "./constants"; -import {UserSigner} from "../userSigner"; +import {PubKeyEncCipher, PubKeyEncNonceLength, PubKeyEncVersion} from "./constants"; export class PubkeyEncryptor { - public static encrypt(data: Buffer, recipientPubKey: UserPublicKey, authSecretKey: UserSecretKey): X25519EncryptedData { + static encrypt(data: Buffer, recipientPubKey: UserPublicKey, authSecretKey: UserSecretKey): X25519EncryptedData { // create a new x225519 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); - const nonce = nacl.randomBytes(24); - const encryptedMessage = nacl.box(data, nonce, recipientDHPubKey, edhPair.secretKey); + const nonce = nacl.randomBytes(PubKeyEncNonceLength); + 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 an elrond ed25519 key which the user interacts with. A signature over H(ciphertext | edhPubKey) From 0f213d91ff19ddcb0dd09c5940ce4e4bcfe31916 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Tue, 16 Aug 2022 13:43:45 +0300 Subject: [PATCH 041/118] add empty line --- src-wallet/crypto/x25519EncryptedData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/crypto/x25519EncryptedData.ts b/src-wallet/crypto/x25519EncryptedData.ts index 21ff2cfdb..4bc3ffbc8 100644 --- a/src-wallet/crypto/x25519EncryptedData.ts +++ b/src-wallet/crypto/x25519EncryptedData.ts @@ -42,4 +42,4 @@ export class X25519EncryptedData { identities: data.identities, }); } -} \ No newline at end of file +} From c6e1cfd151ba410ec67e4edd5a360fa0079b4c84 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Tue, 16 Aug 2022 16:57:42 +0300 Subject: [PATCH 042/118] fix after review --- src-wallet/crypto/pubkeyDecryptor.ts | 4 ++-- src-wallet/crypto/pubkeyEncrypt.spec.ts | 26 ++++++++++++------------- src-wallet/crypto/pubkeyEncryptor.ts | 10 +++++----- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src-wallet/crypto/pubkeyDecryptor.ts b/src-wallet/crypto/pubkeyDecryptor.ts index 9806fcfe0..e9b6d7a00 100644 --- a/src-wallet/crypto/pubkeyDecryptor.ts +++ b/src-wallet/crypto/pubkeyDecryptor.ts @@ -1,8 +1,8 @@ import crypto from "crypto"; import nacl from "tweetnacl"; import ed2curve from "ed2curve"; -import {X25519EncryptedData} from "./x25519EncryptedData"; -import {UserPublicKey, UserSecretKey} from "../userKeys"; +import { X25519EncryptedData } from "./x25519EncryptedData"; +import { UserPublicKey, UserSecretKey } from "../userKeys"; export class PubkeyDecryptor { static decrypt(data: X25519EncryptedData, decryptorSecretKey: UserSecretKey): Buffer { diff --git a/src-wallet/crypto/pubkeyEncrypt.spec.ts b/src-wallet/crypto/pubkeyEncrypt.spec.ts index 53c9cf345..291ca391e 100644 --- a/src-wallet/crypto/pubkeyEncrypt.spec.ts +++ b/src-wallet/crypto/pubkeyEncrypt.spec.ts @@ -1,35 +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"; +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("my secret text for x"); - let encryptedData: X25519EncryptedData; + 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"); - encryptedData = PubkeyEncryptor.encrypt(sensitiveData, new UserPublicKey(bob.address.pubkey()), new UserSecretKey(alice.secretKey)); + encryptedDataOfAliceForBob = PubkeyEncryptor.encrypt(sensitiveData, new UserPublicKey(bob.address.pubkey()), new UserSecretKey(alice.secretKey)); }); it("encrypts/decrypts", () => { - const decryptedData = PubkeyDecryptor.decrypt(encryptedData, new UserSecretKey(bob.secretKey)); + const decryptedData = PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey)); assert.equal(sensitiveData.toString('hex'), decryptedData.toString('hex')); }); it("fails for different originator", () => { - encryptedData.identities.originatorPubKey = carol.address.hex(); - assert.throws(() => PubkeyDecryptor.decrypt(encryptedData, new UserSecretKey(bob.secretKey)), "Invalid authentication for encrypted message 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", () => { - encryptedData.identities.ephemeralPubKey = carol.address.hex(); - assert.throws(() => PubkeyDecryptor.decrypt(encryptedData, new UserSecretKey(bob.secretKey)), "Invalid authentication for encrypted message originator"); + encryptedDataOfAliceForBob.identities.ephemeralPubKey = carol.address.hex(); + assert.throws(() => PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey)), "Invalid authentication for encrypted message originator"); }); }); diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src-wallet/crypto/pubkeyEncryptor.ts index 2dead968d..a72c6fd3d 100644 --- a/src-wallet/crypto/pubkeyEncryptor.ts +++ b/src-wallet/crypto/pubkeyEncryptor.ts @@ -1,9 +1,9 @@ import nacl from "tweetnacl"; import ed2curve from "ed2curve"; -import crypto, {sign} from "crypto"; -import {X25519EncryptedData} from "./x25519EncryptedData"; -import {UserPublicKey, UserSecretKey} from "../userKeys"; -import {PubKeyEncCipher, PubKeyEncNonceLength, PubKeyEncVersion} from "./constants"; +import crypto from "crypto"; +import { X25519EncryptedData } from "./x25519EncryptedData"; +import { UserPublicKey, UserSecretKey } from "../userKeys"; +import { PubKeyEncCipher, PubKeyEncNonceLength, PubKeyEncVersion } from "./constants"; export class PubkeyEncryptor { static encrypt(data: Buffer, recipientPubKey: UserPublicKey, authSecretKey: UserSecretKey): X25519EncryptedData { @@ -19,7 +19,7 @@ export class PubkeyEncryptor { 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 an elrond ed25519 key which the user interacts with. A signature over H(ciphertext | edhPubKey) + // the elrond ed25519 key which the user interacts with. A signature over H(ciphertext | edhPubKey) // would be enough const authSig = crypto.createHash('sha256').update( Buffer.concat([encryptedMessage, edhPair.publicKey]) From 650ac4045c0a75659319f5416bb47c0f9da3527b Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Wed, 24 Aug 2022 10:35:56 +0300 Subject: [PATCH 043/118] fix after review --- src-wallet/crypto/pubkeyEncryptor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src-wallet/crypto/pubkeyEncryptor.ts index a72c6fd3d..4352b5794 100644 --- a/src-wallet/crypto/pubkeyEncryptor.ts +++ b/src-wallet/crypto/pubkeyEncryptor.ts @@ -15,17 +15,17 @@ export class PubkeyEncryptor { } const edhConvertedSecretKey = ed2curve.convertSecretKey(edhPair.secretKey); - const nonce = nacl.randomBytes(PubKeyEncNonceLength); + const nonce = crypto.createHash('sha256').update(data).digest().slice(0, PubKeyEncNonceLength); 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 elrond ed25519 key which the user interacts with. A signature over H(ciphertext | edhPubKey) // would be enough - const authSig = crypto.createHash('sha256').update( + const authMessage = crypto.createHash('sha256').update( Buffer.concat([encryptedMessage, edhPair.publicKey]) ).digest(); - const signature = authSecretKey.sign(authSig); + const signature = authSecretKey.sign(authMessage); return new X25519EncryptedData({ version: PubKeyEncVersion, From b48d3b8fdf487c00d883f05842332bba0c80d89b Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Wed, 24 Aug 2022 11:03:10 +0300 Subject: [PATCH 044/118] implement 'half deterministic nonce' --- src-wallet/crypto/pubkeyEncryptor.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src-wallet/crypto/pubkeyEncryptor.ts index 4352b5794..e63648185 100644 --- a/src-wallet/crypto/pubkeyEncryptor.ts +++ b/src-wallet/crypto/pubkeyEncryptor.ts @@ -15,7 +15,11 @@ export class PubkeyEncryptor { } const edhConvertedSecretKey = ed2curve.convertSecretKey(edhPair.secretKey); - const nonce = crypto.createHash('sha256').update(data).digest().slice(0, PubKeyEncNonceLength); + // 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 From 5b3117951f337e8f40771433d2426415ba1c0905 Mon Sep 17 00:00:00 2001 From: Cristi Corcoveanu Date: Wed, 24 Aug 2022 11:38:58 +0300 Subject: [PATCH 045/118] fixed changelog, export encryptor/decryptor and prepared release --- src-wallet/crypto/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src-wallet/crypto/index.ts b/src-wallet/crypto/index.ts index d674cb91f..183a29c0f 100644 --- a/src-wallet/crypto/index.ts +++ b/src-wallet/crypto/index.ts @@ -1,5 +1,7 @@ export * from "./constants"; export * from "./encryptor"; export * from "./decryptor"; +export * from "./pubkeyEncryptor"; +export * from "./pubkeyDecryptor"; export * from "./encryptedData"; export * from "./randomness"; From b0cb66d4fff996a731b8d0a59800951709bf09b6 Mon Sep 17 00:00:00 2001 From: schimih Date: Mon, 12 Sep 2022 10:35:18 +0300 Subject: [PATCH 046/118] added guardianSigner --- src-wallet/guardianSigner.ts | 47 +++++++++++++++++++ src-wallet/index.ts | 1 + src-wallet/interface.ts | 34 ++++++++++++-- src-wallet/testutils/message.ts | 9 +++- src-wallet/testutils/transaction.ts | 24 +++++++--- src-wallet/userSigner.ts | 13 +++--- src-wallet/users.spec.ts | 70 ++++++++++++++++++++++++----- 7 files changed, 170 insertions(+), 28 deletions(-) create mode 100644 src-wallet/guardianSigner.ts diff --git a/src-wallet/guardianSigner.ts b/src-wallet/guardianSigner.ts new file mode 100644 index 000000000..5cc4b095f --- /dev/null +++ b/src-wallet/guardianSigner.ts @@ -0,0 +1,47 @@ +import { ISignable, ISignature } from "./interface"; +import { UserSecretKey } from "./userKeys"; +import { UserSigner } from "./userSigner"; +import { Signature } from "./signature"; +import { ErrSignerCannotSign } from "./errors"; + +/** + * ed25519 signer + */ +export class GuardianSigner extends UserSigner { + private readonly guardianSecretKey: UserSecretKey; + + constructor(guardianSecretKey: UserSecretKey) { + super(guardianSecretKey) + this.guardianSecretKey = guardianSecretKey + } + + /** + * Signs a message. + * @param signable the message to be signed (e.g. a {@link Transaction}). + */ + async guard(signable: ISignable): Promise { + try { + this.tryGuard(signable); + } catch (err: any) { + throw new ErrSignerCannotSign(err); + } + } + + private tryGuard(signable: ISignable) { + let ownerSignature = signable.getSignature() + let bufferToSign = signable.serializeForSigning(); + let guardianSignatureBuffer = this.guardianSecretKey.sign(bufferToSign); + let guardianSignature = new Signature(guardianSignatureBuffer); + + this.addOwnerSignature(signable, ownerSignature) + this.doApplySignature(signable, guardianSignature); + } + + protected doApplySignature(signable: ISignable, guardianSignature: ISignature): void { + signable.applyGuardianSignature(guardianSignature); + } + + private addOwnerSignature(signable: ISignable, ownerSignature: ISignature) { + signable.applySignature(ownerSignature); + } +} diff --git a/src-wallet/index.ts b/src-wallet/index.ts index c5d518dfd..f24c48cc0 100644 --- a/src-wallet/index.ts +++ b/src-wallet/index.ts @@ -4,5 +4,6 @@ export * from "./userWallet"; export * from "./userKeys"; export * from "./validatorKeys"; export * from "./userSigner"; +export * from "./guardianSigner" export * from "./userVerifier"; export * from "./validatorSigner"; diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts index 04c4fdee3..99806f228 100644 --- a/src-wallet/interface.ts +++ b/src-wallet/interface.ts @@ -23,6 +23,21 @@ export interface ISigner { sign(signable: ISignable): Promise; } +/** + * An interface that defines a signing-capable object. + */ +export interface IGuardianSigner { + /** + * Gets the {@link Address} of the signer. + */ + getAddress(): IAddress; + + /** + * Signs a message (e.g. a transaction). + */ + guard(signable: ISignable): Promise; +} + export interface IVerifier { verify(message: IVerifiable): boolean; } @@ -34,15 +49,26 @@ export interface ISignable { /** * Returns the signable object in its raw form - a sequence of bytes to be signed. */ - serializeForSigning(signedBy: IAddress): Buffer; + serializeForSigning(): Buffer; + + /** + * Returns the signature of the sender. + */ + getSignature(): ISignature; /** * Applies the computed signature on the object itself. * * @param signature The computed signature - * @param signedBy The address of the {@link ISignature} - */ - applySignature(signature: ISignature, signedBy: IAddress): void; + */ + applySignature(signature: ISignature): void; + + /** + * Applies the guardian signature on the transaction. + * + * @param guardianSignature The signature, as computed by a guardian. + */ + applyGuardianSignature(guardianSignature: ISignature): void; } /** diff --git a/src-wallet/testutils/message.ts b/src-wallet/testutils/message.ts index 1fe2f542b..2f60c5f53 100644 --- a/src-wallet/testutils/message.ts +++ b/src-wallet/testutils/message.ts @@ -8,12 +8,13 @@ export class TestMessage implements ISignable, IVerifiable { foo: string = ""; bar: string = ""; signature: string = ""; + guardianSignature: string = ""; constructor(init?: Partial) { Object.assign(this, init); } - serializeForSigning(_signedBy: IAddress): Buffer { + serializeForSigning(): Buffer { let plainObject = { foo: this.foo, bar: this.bar @@ -23,10 +24,14 @@ export class TestMessage implements ISignable, IVerifiable { return Buffer.from(serialized); } - applySignature(signature: ISignature, _signedBy: IAddress): void { + applySignature(signature: ISignature): void { this.signature = signature.hex(); } + applyGuardianSignature(guardianSignature: ISignature): void { + this.guardianSignature = guardianSignature.hex() + } + getSignature(): ISignature { return new Signature(Buffer.from(this.signature, "hex")); } diff --git a/src-wallet/testutils/transaction.ts b/src-wallet/testutils/transaction.ts index bd0d04c06..1291c061c 100644 --- a/src-wallet/testutils/transaction.ts +++ b/src-wallet/testutils/transaction.ts @@ -16,40 +16,50 @@ export class TestTransaction implements ISignable, IVerifiable { options: number = 0; sender: string = ""; + guardian: string = ""; + guardianSignature: string = ""; signature: string = ""; constructor(init?: Partial) { Object.assign(this, init); } - serializeForSigning(signedBy: IAddress): Buffer { - let sender = signedBy.bech32(); + serializeForSigning(): Buffer { let dataEncoded = this.data ? Buffer.from(this.data).toString("base64") : undefined; + let guardian = this.guardian ? this.guardian : undefined; let options = this.options ? this.options : undefined; let plainObject = { nonce: this.nonce, value: this.value, receiver: this.receiver, - sender: sender, + sender: this.sender, + guardian: guardian, gasPrice: this.gasPrice, gasLimit: this.gasLimit, data: dataEncoded, chainID: this.chainID, - version: this.version, - options: options + options: options, + version: this.version }; let serialized = JSON.stringify(plainObject); return Buffer.from(serialized); } - applySignature(signature: ISignature, signedBy: IAddress): void { - this.sender = signedBy.bech32(); + applySignature(signature: ISignature): void { this.signature = signature.hex(); } + applyGuardianSignature(guardianSignature: ISignature): void { + this.guardianSignature = guardianSignature.hex() + } + getSignature(): ISignature { return new Signature(Buffer.from(this.signature, "hex")); } + + getGuardianSignature(): ISignature { + return new Signature(Buffer.from(this.guardianSignature, "hex")); + } } diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index c7cdde4e0..581dcee26 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -1,4 +1,4 @@ -import { IAddress, ISignable, ISigner } from "./interface"; +import { IAddress, ISignable, ISignature, ISigner } from "./interface"; import { Signature } from "./signature"; import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; @@ -23,7 +23,7 @@ export class UserSigner implements ISigner { let secretKey = UserSecretKey.fromPem(text, index); return new UserSigner(secretKey); } - + /** * Signs a message. * @param signable the message to be signed (e.g. a {@link Transaction}). @@ -37,12 +37,15 @@ export class UserSigner implements ISigner { } private trySign(signable: ISignable) { - let signedBy = this.getAddress(); - let bufferToSign = signable.serializeForSigning(signedBy); + let bufferToSign = signable.serializeForSigning(); let signatureBuffer = this.secretKey.sign(bufferToSign); let signature = new Signature(signatureBuffer); - signable.applySignature(signature, signedBy); + this.doApplySignature(signable, signature); + } + + protected doApplySignature(signable: ISignable, signature: ISignature) { + signable.applySignature(signature); } /** diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index ee71c03e5..3a9f3e846 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -5,6 +5,7 @@ import { UserWallet } from "./userWallet"; import { Randomness } from "./crypto"; import { UserAddress } from "./userAddress"; import { UserSigner } from "./userSigner"; +import { GuardianSigner } from "./guardianSigner" import { UserVerifier } from "./userVerifier"; import { ErrInvariantFailed } from "./errors"; import { TestMessage } from "./testutils/message"; @@ -122,8 +123,7 @@ describe("test user wallets", () => { it("should sign transactions", async () => { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); - let sender = UserAddress.fromBech32("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); - + // With data field let transaction = new TestTransaction({ nonce: 0, @@ -132,14 +132,14 @@ describe("test user wallets", () => { gasPrice: 1000000000, gasLimit: 50000, data: "foo", - chainID: "1" + chainID: "1", }); - let serialized = transaction.serializeForSigning(sender).toString(); + let serialized = transaction.serializeForSigning().toString(); await signer.sign(transaction); - assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); - assert.equal(transaction.getSignature().hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); + assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); + assert.equal(transaction.getSignature().hex(), "a3b61a2fe461f3393c42e6cb0477a6b52ffd92168f10c111f6aa8d0a310ee0c314fae0670f8313f1ad992933ac637c61a8ff20cc20b6a8b2260a4af1a120a70d"); assert.isTrue(verifier.verify(transaction)); // Without data field transaction = new TestTransaction({ @@ -151,11 +151,61 @@ describe("test user wallets", () => { chainID: "1" }); - serialized = transaction.serializeForSigning(sender).toString(); + serialized = transaction.serializeForSigning().toString(); + await signer.sign(transaction); + + assert.equal(serialized, `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); + assert.equal(transaction.getSignature().hex(), "f136c901d37349a7da8cfe3ab5ec8ef333b0bc351517c0e9bef9eb9704aed3077bf222769cade5ff29dffe5f42e4f0c5e0b068bdba90cd2cb41da51fd45d5a03"); + }); + + it("guardian should sign transactions from PEM", async () => { + // bob is the guardian + let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); + let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); + let guardianSigner = new GuardianSigner(UserSecretKey.fromPem(bob.pemFileText)); + + // With data field + let transaction = new TestTransaction({ + nonce: 0, + value: "0", + receiver: "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + sender: "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", + gasPrice: 1000000000, + gasLimit: 50000, + data: "foo", + chainID: "1", + guardian: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + options: 2, + version: 2 + }); + + let serialized = transaction.serializeForSigning().toString(); + await signer.sign(transaction); + await guardianSigner.sign(transaction); + + assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","options":2,"version":2}`); + assert.equal(transaction.getSignature().hex(), "00b867ae749616954711ef227c0a3f5c6556246f26dbde12ad929a099094065341a0fae7c5ced98e6bdd100ce922c975667444ea859dce9597b46e63cade2a03"); + assert.equal(transaction.getGuardianSignature().hex(), "1326e44941ef7bfbad3edf346e72abe23704ee32b4b6a6a6a9b793bd7c62b6d4a69d3c6ea2dddf7eabc8df8fe291cd24822409ab9194b6a0f3bbbf1c59b0a10f"); + assert.isTrue(verifier.verify(transaction)); + // Without data field + transaction = new TestTransaction({ + nonce: 8, + value: "10000000000000000000", + receiver: "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", + sender: "erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz", + gasPrice: 1000000000, + gasLimit: 50000, + chainID: "1", + guardian: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + options: 2, + version: 2, + }); + + serialized = transaction.serializeForSigning().toString(); await signer.sign(transaction); - assert.equal(serialized, `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); - assert.equal(transaction.getSignature().hex(), "4a6d8186eae110894e7417af82c9bf9592696c0600faf110972e0e5310d8485efc656b867a2336acec2b4c1e5f76c9cc70ba1803c6a46455ed7f1e2989a90105"); + assert.equal(serialized, `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","options":2,"version":2}`); + assert.equal(transaction.getSignature().hex(), "49a63fa0e3cfb81a2b6d926c741328fb270ea4f58fa32585fe8aa3cde191245e5a13c5c059d5576f4c05fc24d2534a2124ff79c98d067ce8412c806779066b03"); }); it("should sign transactions using PEM files", async () => { @@ -172,7 +222,7 @@ describe("test user wallets", () => { }); await signer.sign(transaction); - assert.equal(transaction.getSignature().hex(), "c0bd2b3b33a07b9cc5ee7435228acb0936b3829c7008aacabceea35163e555e19a34def2c03a895cf36b0bcec30a7e11215c11efc0da29294a11234eb2b3b906"); + assert.equal(transaction.getSignature().hex(), "ba4fa95fea1402e4876abf1d5a510615aab374ee48bb76f5230798a7d3f2fcae6ba91ba56c6d62e6e7003ce531ff02f219cb7218dd00dd2ca650ba747f19640a"); }); it("signs a general message", function () { From a96e5ca13ca9769c0cf6719d3c8c0f5a1937ab45 Mon Sep 17 00:00:00 2001 From: schimih Date: Mon, 12 Sep 2022 11:06:58 +0300 Subject: [PATCH 047/118] minor fix --- src-wallet/guardianSigner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/guardianSigner.ts b/src-wallet/guardianSigner.ts index 5cc4b095f..0aa2f377c 100644 --- a/src-wallet/guardianSigner.ts +++ b/src-wallet/guardianSigner.ts @@ -15,7 +15,7 @@ export class GuardianSigner extends UserSigner { this.guardianSecretKey = guardianSecretKey } - /** +/** * Signs a message. * @param signable the message to be signed (e.g. a {@link Transaction}). */ From 88c5f9109d37aed360238c82e34107e64ced82cf Mon Sep 17 00:00:00 2001 From: schimih Date: Wed, 19 Oct 2022 12:02:46 +0300 Subject: [PATCH 048/118] fixes after review --- src-wallet/userSigner.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 581dcee26..57dd6f8ec 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -41,10 +41,10 @@ export class UserSigner implements ISigner { let signatureBuffer = this.secretKey.sign(bufferToSign); let signature = new Signature(signatureBuffer); - this.doApplySignature(signable, signature); + this.applySignature(signable, signature); } - protected doApplySignature(signable: ISignable, signature: ISignature) { + protected applySignature(signable: ISignable, signature: ISignature) { signable.applySignature(signature); } From 201be3987abdd669435989e1ba4fb714f956908d Mon Sep 17 00:00:00 2001 From: schimih Date: Wed, 19 Oct 2022 12:07:14 +0300 Subject: [PATCH 049/118] fix --- src-wallet/userSigner.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 57dd6f8ec..0dea38ecd 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -41,10 +41,6 @@ export class UserSigner implements ISigner { let signatureBuffer = this.secretKey.sign(bufferToSign); let signature = new Signature(signatureBuffer); - this.applySignature(signable, signature); - } - - protected applySignature(signable: ISignable, signature: ISignature) { signable.applySignature(signature); } From dac65bcff5abdf15ac0b4a963578c72b5f4e0f96 Mon Sep 17 00:00:00 2001 From: schimih Date: Wed, 19 Oct 2022 13:21:24 +0300 Subject: [PATCH 050/118] changed `guardian.sign()` to `guardian.guard()` --- src-wallet/users.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 3a9f3e846..a499f1826 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -181,7 +181,7 @@ describe("test user wallets", () => { let serialized = transaction.serializeForSigning().toString(); await signer.sign(transaction); - await guardianSigner.sign(transaction); + await guardianSigner.guard(transaction); assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","options":2,"version":2}`); assert.equal(transaction.getSignature().hex(), "00b867ae749616954711ef227c0a3f5c6556246f26dbde12ad929a099094065341a0fae7c5ced98e6bdd100ce922c975667444ea859dce9597b46e63cade2a03"); From ec31e5e2265454e371768d50fb954d457bec59c0 Mon Sep 17 00:00:00 2001 From: schimih Date: Wed, 19 Oct 2022 14:43:00 +0300 Subject: [PATCH 051/118] fixes after review --- src-wallet/guardianSigner.ts | 24 +++++++++++------------- src-wallet/testutils/message.ts | 4 ++-- src-wallet/testutils/transaction.ts | 14 +++++++------- src-wallet/userSigner.ts | 2 +- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src-wallet/guardianSigner.ts b/src-wallet/guardianSigner.ts index 0aa2f377c..39d65751a 100644 --- a/src-wallet/guardianSigner.ts +++ b/src-wallet/guardianSigner.ts @@ -8,17 +8,15 @@ import { ErrSignerCannotSign } from "./errors"; * ed25519 signer */ export class GuardianSigner extends UserSigner { - private readonly guardianSecretKey: UserSecretKey; - constructor(guardianSecretKey: UserSecretKey) { - super(guardianSecretKey) - this.guardianSecretKey = guardianSecretKey + constructor(secretKey: UserSecretKey) { + super(secretKey) } -/** - * Signs a message. - * @param signable the message to be signed (e.g. a {@link Transaction}). - */ + /** + * Signs a message. + * @param signable the message to be signed (e.g. a {@link Transaction}). + */ async guard(signable: ISignable): Promise { try { this.tryGuard(signable); @@ -28,16 +26,16 @@ export class GuardianSigner extends UserSigner { } private tryGuard(signable: ISignable) { - let ownerSignature = signable.getSignature() - let bufferToSign = signable.serializeForSigning(); - let guardianSignatureBuffer = this.guardianSecretKey.sign(bufferToSign); - let guardianSignature = new Signature(guardianSignatureBuffer); + const ownerSignature = signable.getSignature() + const bufferToSign = signable.serializeForSigning(); + const guardianSignatureBuffer = this.secretKey.sign(bufferToSign); + const guardianSignature = new Signature(guardianSignatureBuffer); this.addOwnerSignature(signable, ownerSignature) this.doApplySignature(signable, guardianSignature); } - protected doApplySignature(signable: ISignable, guardianSignature: ISignature): void { + protected doApplySignature(signable: ISignable, guardianSignature: ISignature) { signable.applyGuardianSignature(guardianSignature); } diff --git a/src-wallet/testutils/message.ts b/src-wallet/testutils/message.ts index 2f60c5f53..7ed55520d 100644 --- a/src-wallet/testutils/message.ts +++ b/src-wallet/testutils/message.ts @@ -24,11 +24,11 @@ export class TestMessage implements ISignable, IVerifiable { return Buffer.from(serialized); } - applySignature(signature: ISignature): void { + applySignature(signature: ISignature) { this.signature = signature.hex(); } - applyGuardianSignature(guardianSignature: ISignature): void { + applyGuardianSignature(guardianSignature: ISignature) { this.guardianSignature = guardianSignature.hex() } diff --git a/src-wallet/testutils/transaction.ts b/src-wallet/testutils/transaction.ts index 1291c061c..e09ee2194 100644 --- a/src-wallet/testutils/transaction.ts +++ b/src-wallet/testutils/transaction.ts @@ -25,11 +25,11 @@ export class TestTransaction implements ISignable, IVerifiable { } serializeForSigning(): Buffer { - let dataEncoded = this.data ? Buffer.from(this.data).toString("base64") : undefined; - let guardian = this.guardian ? this.guardian : undefined; - let options = this.options ? this.options : undefined; + const dataEncoded = this.data ? Buffer.from(this.data).toString("base64") : undefined; + const guardian = this.guardian ? this.guardian : undefined; + const options = this.options ? this.options : undefined; - let plainObject = { + const plainObject = { nonce: this.nonce, value: this.value, receiver: this.receiver, @@ -43,15 +43,15 @@ export class TestTransaction implements ISignable, IVerifiable { version: this.version }; - let serialized = JSON.stringify(plainObject); + const serialized = JSON.stringify(plainObject); return Buffer.from(serialized); } - applySignature(signature: ISignature): void { + applySignature(signature: ISignature) { this.signature = signature.hex(); } - applyGuardianSignature(guardianSignature: ISignature): void { + applyGuardianSignature(guardianSignature: ISignature) { this.guardianSignature = guardianSignature.hex() } diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 0dea38ecd..24f74e07a 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -8,7 +8,7 @@ import { ErrSignerCannotSign } from "./errors"; * ed25519 signer */ export class UserSigner implements ISigner { - private readonly secretKey: UserSecretKey; + protected readonly secretKey: UserSecretKey; constructor(secretKey: UserSecretKey) { this.secretKey = secretKey; From 1ab3c697688a6efe1b46483e85bae964c235dae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 6 Jan 2023 19:33:08 +0200 Subject: [PATCH 052/118] Elrond to MultiversX (part 1). --- src-wallet/crypto/pubkeyEncryptor.ts | 12 ++++++------ src-wallet/userWallet.ts | 16 ++++++++-------- src-wallet/validators.spec.ts | 2 -- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src-wallet/crypto/pubkeyEncryptor.ts index e63648185..b3f6517c1 100644 --- a/src-wallet/crypto/pubkeyEncryptor.ts +++ b/src-wallet/crypto/pubkeyEncryptor.ts @@ -1,9 +1,9 @@ -import nacl from "tweetnacl"; -import ed2curve from "ed2curve"; import crypto from "crypto"; -import { X25519EncryptedData } from "./x25519EncryptedData"; +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 { @@ -17,13 +17,13 @@ export class PubkeyEncryptor { // 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 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 elrond ed25519 key which the user interacts with. A signature over H(ciphertext | edhPubKey) + // 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]) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 485e97a3c..8141b8983 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -1,13 +1,13 @@ -import { UserPublicKey, UserSecretKey } from "./userKeys"; -import { EncryptedData, Encryptor, Decryptor, CipherAlgorithm, Version, KeyDerivationFunction, Randomness } from "./crypto"; +import { CipherAlgorithm, Decryptor, EncryptedData, Encryptor, KeyDerivationFunction, Randomness, Version } from "./crypto"; import { ScryptKeyDerivationParams } from "./crypto/derivationParams"; +import { UserPublicKey, UserSecretKey } from "./userKeys"; export class UserWallet { private readonly publicKey: UserPublicKey; private readonly encryptedData: EncryptedData; /** - * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L76 + * Copied from: https://github.com/multiversx/mx-deprecated-core-js/blob/v1.28.0/src/account.js#L76 * Notes: adjustements (code refactoring, no change in logic), in terms of: * - typing (since this is the TypeScript version) * - error handling (in line with erdjs's error system) @@ -24,7 +24,7 @@ export class UserWallet { } /** - * Copied from: https://github.com/ElrondNetwork/elrond-core-js/blob/v1.28.0/src/account.js#L42 + * Copied from: https://github.com/multiversx/mx-deprecated-core-js/blob/v1.28.0/src/account.js#L42 * Notes: adjustements (code refactoring, no change in logic), in terms of: * - typing (since this is the TypeScript version) * - error handling (in line with erdjs's error system) @@ -55,10 +55,10 @@ export class UserWallet { iv: keyfileObject.crypto.cipherparams.iv, kdf: keyfileObject.crypto.kdf, kdfparams: new ScryptKeyDerivationParams( - keyfileObject.crypto.kdfparams.n, - keyfileObject.crypto.kdfparams.r, - keyfileObject.crypto.kdfparams.p, - keyfileObject.crypto.kdfparams.dklen + keyfileObject.crypto.kdfparams.n, + keyfileObject.crypto.kdfparams.r, + keyfileObject.crypto.kdfparams.p, + keyfileObject.crypto.kdfparams.dklen ), salt: keyfileObject.crypto.kdfparams.salt, mac: keyfileObject.crypto.mac, diff --git a/src-wallet/validators.spec.ts b/src-wallet/validators.spec.ts index 609098cca..dee84cf8f 100644 --- a/src-wallet/validators.spec.ts +++ b/src-wallet/validators.spec.ts @@ -11,7 +11,6 @@ describe("test validator keys", () => { assert.equal(key.generatePublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); let signature = key.sign(Buffer.from("hello")); - // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` assert.equal(signature.toString("hex"), "84fd0a3a9d4f1ea2d4b40c6da67f9b786284a1c3895b7253fec7311597cda3f757862bb0690a92a13ce612c33889fd86"); secretKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); @@ -19,7 +18,6 @@ describe("test validator keys", () => { assert.equal(key.generatePublicKey().hex(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); signature = key.sign(Buffer.from("hello")); - // Expected signature computed using `elrond-sdk-go-tools/cmd/signer/mcl_signer.go` assert.equal(signature.toString("hex"), "be2e593ff10899a2ee8e1d5c8094e36c9f48e04b87e129991ff09475808743e07bb41bf6e7bc1463fa554c4b46594b98"); }); From 6e2f2c360addc48e3bc32af59a36c628ce1caa26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 11 Jan 2023 11:34:11 +0200 Subject: [PATCH 053/118] Rename package: sdk-wallet. --- src-wallet/crypto/constants.ts | 2 +- src-wallet/userWallet.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src-wallet/crypto/constants.ts b/src-wallet/crypto/constants.ts index 07a4ca7c4..f99a1c365 100644 --- a/src-wallet/crypto/constants.ts +++ b/src-wallet/crypto/constants.ts @@ -1,4 +1,4 @@ -// In a future PR, improve versioning infrastructure for key-file objects in erdjs. +// In a future PR, improve versioning infrastructure for key-file objects. export const Version = 4; export const CipherAlgorithm = "aes-128-ctr"; export const DigestAlgorithm = "sha256"; diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 8141b8983..d365b8c31 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -10,7 +10,7 @@ export class UserWallet { * Copied from: https://github.com/multiversx/mx-deprecated-core-js/blob/v1.28.0/src/account.js#L76 * Notes: adjustements (code refactoring, no change in logic), in terms of: * - typing (since this is the TypeScript version) - * - error handling (in line with erdjs's error system) + * - error handling (in line with sdk-core's error system) * - references to crypto functions * - references to object members * @@ -27,7 +27,7 @@ export class UserWallet { * Copied from: https://github.com/multiversx/mx-deprecated-core-js/blob/v1.28.0/src/account.js#L42 * Notes: adjustements (code refactoring, no change in logic), in terms of: * - typing (since this is the TypeScript version) - * - error handling (in line with erdjs's error system) + * - error handling (in line with sdk-core's error system) * - references to crypto functions * - references to object members * From 570dfceb0ccef5c932b69214a8c3e9cb18a2d3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 11 Jan 2023 18:03:07 +0200 Subject: [PATCH 054/118] Use sdk-bls-wasm from new organization. --- src-wallet/validatorKeys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/validatorKeys.ts b/src-wallet/validatorKeys.ts index 441c4ac07..bd77ae2f9 100644 --- a/src-wallet/validatorKeys.ts +++ b/src-wallet/validatorKeys.ts @@ -2,7 +2,7 @@ import { guardLength } from "./assertions"; import { ErrInvariantFailed } from "./errors"; import { parseValidatorKey } from "./pem"; -const bls = require('@elrondnetwork/bls-wasm'); +const bls = require('@multiversx/sdk-bls-wasm'); export const VALIDATOR_SECRETKEY_LENGTH = 32; export const VALIDATOR_PUBKEY_LENGTH = 96; From a988993023d126e947c3916f0b281031c7abaea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 26 Jan 2023 10:37:35 +0200 Subject: [PATCH 055/118] Sketch wallet.json with mnemonic / arbitrary data. --- src-wallet/crypto/constants.ts | 2 - src-wallet/crypto/encrypt.spec.ts | 8 +- src-wallet/crypto/encryptor.ts | 14 +- src-wallet/userWallet.ts | 248 +++++++++++++++++++++++++----- src-wallet/users.spec.ts | 64 ++++---- 5 files changed, 263 insertions(+), 73 deletions(-) diff --git a/src-wallet/crypto/constants.ts b/src-wallet/crypto/constants.ts index f99a1c365..9d29781d0 100644 --- a/src-wallet/crypto/constants.ts +++ b/src-wallet/crypto/constants.ts @@ -1,5 +1,3 @@ -// In a future PR, improve versioning infrastructure for key-file objects. -export const Version = 4; export const CipherAlgorithm = "aes-128-ctr"; export const DigestAlgorithm = "sha256"; export const KeyDerivationFunction = "scrypt"; diff --git a/src-wallet/crypto/encrypt.spec.ts b/src-wallet/crypto/encrypt.spec.ts index b076b3111..4c5f1c517 100644 --- a/src-wallet/crypto/encrypt.spec.ts +++ b/src-wallet/crypto/encrypt.spec.ts @@ -1,12 +1,12 @@ import { assert } from "chai"; -import { Encryptor } from "./encryptor"; import { Decryptor } from "./decryptor"; import { EncryptedData } from "./encryptedData"; +import { Encryptor } from "./encryptor"; describe("test address", () => { - it("encrypts/decrypts", () => { + it("encrypts/decrypts", () => { const sensitiveData = Buffer.from("my mnemonic"); - const encryptedData = Encryptor.encrypt(sensitiveData, "password123"); + const encryptedData = Encryptor.encrypt(7, sensitiveData, "password123"); const decryptedBuffer = Decryptor.decrypt(encryptedData, "password123"); assert.equal(sensitiveData.toString('hex'), decryptedBuffer.toString('hex')); @@ -14,7 +14,7 @@ describe("test address", () => { it("encodes/decodes kdfparams", () => { const sensitiveData = Buffer.from("my mnemonic"); - const encryptedData = Encryptor.encrypt(sensitiveData, "password123"); + const encryptedData = Encryptor.encrypt(7, sensitiveData, "password123"); const decodedData = EncryptedData.fromJSON(encryptedData.toJSON()); assert.deepEqual(decodedData, encryptedData, "invalid decoded data"); diff --git a/src-wallet/crypto/encryptor.ts b/src-wallet/crypto/encryptor.ts index 33379cbc3..c0708acfe 100644 --- a/src-wallet/crypto/encryptor.ts +++ b/src-wallet/crypto/encryptor.ts @@ -1,11 +1,15 @@ import crypto from "crypto"; -import { Randomness } from "./randomness"; +import { CipherAlgorithm, DigestAlgorithm, KeyDerivationFunction } from "./constants"; import { ScryptKeyDerivationParams } from "./derivationParams"; -import { CipherAlgorithm, DigestAlgorithm, Version, KeyDerivationFunction } from "./constants"; -import {EncryptedData} from "./encryptedData"; +import { EncryptedData } from "./encryptedData"; +import { Randomness } from "./randomness"; + +export enum EncryptorVersion { + V4 = 4, +} export class Encryptor { - static encrypt(data: Buffer, password: string, randomness: Randomness = new Randomness()): EncryptedData { + static encrypt(version: EncryptorVersion, data: Buffer, password: string, randomness: Randomness = new Randomness()): EncryptedData { const kdParams = new ScryptKeyDerivationParams(); const derivedKey = kdParams.generateDerivedKey(Buffer.from(password), randomness.salt); const derivedKeyFirstHalf = derivedKey.slice(0, 16); @@ -16,7 +20,7 @@ export class Encryptor { const mac = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest(); return new EncryptedData({ - version: Version, + version: version, id: randomness.id, ciphertext: ciphertext.toString('hex'), iv: randomness.iv.toString('hex'), diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index d365b8c31..f9866a1aa 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -1,26 +1,125 @@ -import { CipherAlgorithm, Decryptor, EncryptedData, Encryptor, KeyDerivationFunction, Randomness, Version } from "./crypto"; +import { CipherAlgorithm, Decryptor, EncryptedData, Encryptor, EncryptorVersion, KeyDerivationFunction, Randomness } from "./crypto"; import { ScryptKeyDerivationParams } from "./crypto/derivationParams"; +import { Err } from "./errors"; import { UserPublicKey, UserSecretKey } from "./userKeys"; +export enum EnvelopeVersion { + // Does not have the "kind" field, and is meant to hold the **secret key**. + // The "crypto" section is not versioned. + V4 = 4, + // Has the "kind" field, and is meant to hold the **secret key** or **the mnemonic** (or any other secret payload). + // Furthermore, the "crypto" section is versioned separately. + V5 = 5 +} + +export enum UserWalletKind { + SecretKey = "secretKey", + Mnemonic = "mnemonic", + Arbitrary = "arbitrary" +} + export class UserWallet { - private readonly publicKey: UserPublicKey; + private readonly envelopeVersion: number; + private readonly kind: UserWalletKind; private readonly encryptedData: EncryptedData; + private readonly publicKeyWhenKindIsSecretKey?: UserPublicKey; - /** - * Copied from: https://github.com/multiversx/mx-deprecated-core-js/blob/v1.28.0/src/account.js#L76 - * Notes: adjustements (code refactoring, no change in logic), in terms of: - * - typing (since this is the TypeScript version) - * - error handling (in line with sdk-core's error system) - * - references to crypto functions - * - references to object members - * - * Given a password, generates the contents for a file containing the account's secret key, - * passed through a password-based key derivation function (kdf). - */ - constructor(secretKey: UserSecretKey, password: string, randomness: Randomness = new Randomness()) { - const text = Buffer.concat([secretKey.valueOf(), secretKey.generatePublicKey().valueOf()]); - this.encryptedData = Encryptor.encrypt(text, password, randomness); - this.publicKey = secretKey.generatePublicKey(); + private constructor({ + envelopeVersion: envelopeVersion, + kind, + encryptedData, + publicKeyWhenKindIsSecretKey + }: { + envelopeVersion: EnvelopeVersion; + kind: UserWalletKind; + encryptedData: EncryptedData; + publicKeyWhenKindIsSecretKey?: UserPublicKey; + }) { + this.envelopeVersion = envelopeVersion; + this.kind = kind; + this.encryptedData = encryptedData; + this.publicKeyWhenKindIsSecretKey = publicKeyWhenKindIsSecretKey; + } + + static fromSecretKey({ + envelopeVersion, + encryptorVersion, + secretKey, + password, + randomness, + }: { + envelopeVersion?: EnvelopeVersion; + encryptorVersion?: EncryptorVersion; + secretKey: UserSecretKey; + password: string; + randomness?: Randomness; + }): UserWallet { + envelopeVersion = envelopeVersion || EnvelopeVersion.V4; + encryptorVersion = encryptorVersion || EncryptorVersion.V4; + randomness = randomness || new Randomness(); + + const publicKey = secretKey.generatePublicKey(); + const text = Buffer.concat([secretKey.valueOf(), publicKey.valueOf()]); + const encryptedData = Encryptor.encrypt(encryptorVersion, text, password, randomness); + + return new UserWallet({ + envelopeVersion: envelopeVersion, + kind: UserWalletKind.SecretKey, + encryptedData, + publicKeyWhenKindIsSecretKey: publicKey + }); + } + + static fromMnemonic({ + envelopeVersion, + encryptorVersion, + mnemonic, + password, + randomness, + }: { + envelopeVersion?: EnvelopeVersion; + encryptorVersion?: EncryptorVersion; + mnemonic: string; + password: string; + randomness?: Randomness; + }): UserWallet { + envelopeVersion = envelopeVersion || EnvelopeVersion.V5; + encryptorVersion = encryptorVersion || EncryptorVersion.V4; + randomness = randomness || new Randomness(); + + const encryptedData = Encryptor.encrypt(encryptorVersion, Buffer.from(mnemonic), password, randomness); + + return new UserWallet({ + envelopeVersion: envelopeVersion, + kind: UserWalletKind.Mnemonic, + encryptedData + }); + } + + static fromArbitrary({ + envelopeVersion, + encryptorVersion, + arbitraryData, + password, + randomness, + }: { + envelopeVersion?: EnvelopeVersion; + encryptorVersion?: EncryptorVersion; + arbitraryData: Buffer; + password: string; + randomness?: Randomness; + }): UserWallet { + envelopeVersion = envelopeVersion || EnvelopeVersion.V5; + encryptorVersion = encryptorVersion || EncryptorVersion.V4; + randomness = randomness || new Randomness(); + + const encryptedData = Encryptor.encrypt(encryptorVersion, arbitraryData, password, randomness); + + return new UserWallet({ + envelopeVersion: envelopeVersion, + kind: UserWalletKind.Arbitrary, + encryptedData + }); } /** @@ -34,6 +133,10 @@ export class UserWallet { * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { + if (keyFileObject.version >= EnvelopeVersion.V5) { + this.requireKind(keyFileObject.kind, UserWalletKind.SecretKey, "decryptSecretKey") + } + const encryptedData = UserWallet.edFromJSON(keyFileObject); let text = Decryptor.decrypt(encryptedData, password); @@ -42,13 +145,37 @@ export class UserWallet { text = Buffer.concat([zeroPadding, text]); } - let seed = text.slice(0, 32); + const seed = text.slice(0, 32); return new UserSecretKey(seed); } + static decryptMnemonic(keyFileObject: any, password: string): string { + this.requireV5OrHigher(keyFileObject.version, "decryptMnemonic"); + this.requireKind(keyFileObject.kind, UserWalletKind.Mnemonic, "decryptMnemonic") + + const encryptedData = UserWallet.edFromJSON(keyFileObject); + const text = Decryptor.decrypt(encryptedData, password); + return text.toString(); + } + + static decryptArbitrary(keyFileObject: any, password: string): Buffer { + this.requireV5OrHigher(keyFileObject.version, "decryptArbitrary"); + this.requireKind(keyFileObject.kind, UserWalletKind.Arbitrary, "decryptArbitrary") + + const encryptedData = UserWallet.edFromJSON(keyFileObject); + const data = Decryptor.decrypt(encryptedData, password); + return data; + } + static edFromJSON(keyfileObject: any): EncryptedData { + const encryptorVersion: number = (keyfileObject.version == EnvelopeVersion.V4) ? + // In V4, the "crypto" section inherits the version from the envelope. + EncryptorVersion.V4 : + // In V5, the "crypto" section has its own version. + keyfileObject.crypto.version; + return new EncryptedData({ - version: Version, + version: encryptorVersion, id: keyfileObject.id, cipher: keyfileObject.crypto.cipher, ciphertext: keyfileObject.crypto.ciphertext, @@ -69,25 +196,74 @@ export class UserWallet { * Converts the encrypted keyfile to plain JavaScript object. */ toJSON(): any { + if (this.kind == UserWalletKind.SecretKey) { + return this.getEnvelopeWhenKindIsSecretKey(); + } + + return this.getEnvelopeWhenKindIsMnemonicOrArbitrary(); + } + + getEnvelopeWhenKindIsSecretKey(): any { + if (!this.publicKeyWhenKindIsSecretKey) { + throw new Err("Public key isn't available"); + } + + const cryptoSection = this.getCryptoSectionAsJSON(); + + const envelope: any = { + version: this.envelopeVersion, + // Adding "kind", if appropriate. + ...(this.envelopeVersion >= 5 ? { kind: UserWalletKind.SecretKey } : {}), + id: this.encryptedData.id, + address: this.publicKeyWhenKindIsSecretKey.hex(), + bech32: this.publicKeyWhenKindIsSecretKey.toAddress().toString(), + crypto: cryptoSection + }; + + return envelope; + } + + getCryptoSectionAsJSON(): any { + const cryptoSection: any = { + // Adding "version", if appropriate. + ...(this.envelopeVersion >= 5 ? { version: this.encryptedData.version } : {}), + ciphertext: this.encryptedData.ciphertext, + cipherparams: { iv: this.encryptedData.iv }, + cipher: CipherAlgorithm, + kdf: KeyDerivationFunction, + kdfparams: { + dklen: this.encryptedData.kdfparams.dklen, + salt: this.encryptedData.salt, + n: this.encryptedData.kdfparams.n, + r: this.encryptedData.kdfparams.r, + p: this.encryptedData.kdfparams.p + }, + mac: this.encryptedData.mac, + }; + + return cryptoSection; + } + + getEnvelopeWhenKindIsMnemonicOrArbitrary(): any { + const cryptoSection = this.getCryptoSectionAsJSON(); + return { - version: Version, + version: this.envelopeVersion, id: this.encryptedData.id, - address: this.publicKey.hex(), - bech32: this.publicKey.toAddress().toString(), - crypto: { - ciphertext: this.encryptedData.ciphertext, - cipherparams: { iv: this.encryptedData.iv }, - cipher: CipherAlgorithm, - kdf: KeyDerivationFunction, - kdfparams: { - dklen: this.encryptedData.kdfparams.dklen, - salt: this.encryptedData.salt, - n: this.encryptedData.kdfparams.n, - r: this.encryptedData.kdfparams.r, - p: this.encryptedData.kdfparams.p - }, - mac: this.encryptedData.mac, - } + kind: this.kind, + crypto: cryptoSection }; } + + private static requireKind(kind: UserWalletKind, expectedKind: UserWalletKind, context: string) { + if (kind != expectedKind) { + throw new Err(`Expected kind to be ${expectedKind}, but it was ${kind}. Context: ${context}`); + } + } + + private static requireV5OrHigher(version: EnvelopeVersion, context: string) { + if (version < EnvelopeVersion.V5) { + throw new Err(`Unsupported version: ${version}. Context: ${context}`); + } + } } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index ee71c03e5..8dcf00b69 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -1,15 +1,15 @@ import { assert } from "chai"; -import { UserSecretKey } from "./userKeys"; -import { Mnemonic } from "./mnemonic"; -import { UserWallet } from "./userWallet"; import { Randomness } from "./crypto"; -import { UserAddress } from "./userAddress"; -import { UserSigner } from "./userSigner"; -import { UserVerifier } from "./userVerifier"; import { ErrInvariantFailed } from "./errors"; +import { Mnemonic } from "./mnemonic"; import { TestMessage } from "./testutils/message"; -import { DummyMnemonic, DummyPassword, loadTestWallet, TestWallet } from "./testutils/wallets"; import { TestTransaction } from "./testutils/transaction"; +import { DummyMnemonic, DummyPassword, loadTestWallet, TestWallet } from "./testutils/wallets"; +import { UserAddress } from "./userAddress"; +import { UserSecretKey } from "./userKeys"; +import { UserSigner } from "./userSigner"; +import { UserVerifier } from "./userVerifier"; +import { UserWallet } from "./userWallet"; describe("test user wallets", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; @@ -79,9 +79,9 @@ describe("test user wallets", () => { let carolSecretKey = UserSecretKey.fromString(carol.secretKeyHex); console.time("encrypt"); - let aliceKeyFile = new UserWallet(aliceSecretKey, password); - let bobKeyFile = new UserWallet(bobSecretKey, password); - let carolKeyFile = new UserWallet(carolSecretKey, password); + let aliceKeyFile = UserWallet.fromSecretKey({ secretKey: aliceSecretKey, password: password }); + let bobKeyFile = UserWallet.fromSecretKey({ secretKey: bobSecretKey, password: password }); + let carolKeyFile = UserWallet.fromSecretKey({ secretKey: carolSecretKey, password: password }); console.timeEnd("encrypt"); assert.equal(aliceKeyFile.toJSON().bech32, alice.address.bech32()); @@ -96,23 +96,35 @@ describe("test user wallets", () => { // With provided randomness, in order to reproduce our development wallets - aliceKeyFile = new UserWallet(aliceSecretKey, password, new Randomness({ - id: alice.keyFileObject.id, - iv: Buffer.from(alice.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(alice.keyFileObject.crypto.kdfparams.salt, "hex") - })); + aliceKeyFile = UserWallet.fromSecretKey({ + secretKey: aliceSecretKey, + password: password, + randomness: new Randomness({ + id: alice.keyFileObject.id, + iv: Buffer.from(alice.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(alice.keyFileObject.crypto.kdfparams.salt, "hex") + }) + }); - bobKeyFile = new UserWallet(bobSecretKey, password, new Randomness({ - id: bob.keyFileObject.id, - iv: Buffer.from(bob.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(bob.keyFileObject.crypto.kdfparams.salt, "hex") - })); + bobKeyFile = UserWallet.fromSecretKey({ + secretKey: bobSecretKey, + password: password, + randomness: new Randomness({ + id: bob.keyFileObject.id, + iv: Buffer.from(bob.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(bob.keyFileObject.crypto.kdfparams.salt, "hex") + }) + }); - carolKeyFile = new UserWallet(carolSecretKey, password, new Randomness({ - id: carol.keyFileObject.id, - iv: Buffer.from(carol.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(carol.keyFileObject.crypto.kdfparams.salt, "hex") - })); + carolKeyFile = UserWallet.fromSecretKey({ + secretKey: carolSecretKey, + password: password, + randomness: new Randomness({ + id: carol.keyFileObject.id, + iv: Buffer.from(carol.keyFileObject.crypto.cipherparams.iv, "hex"), + salt: Buffer.from(carol.keyFileObject.crypto.kdfparams.salt, "hex") + }) + }); assert.deepEqual(aliceKeyFile.toJSON(), alice.keyFileObject); assert.deepEqual(bobKeyFile.toJSON(), bob.keyFileObject); @@ -123,7 +135,7 @@ describe("test user wallets", () => { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); let sender = UserAddress.fromBech32("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); - + // With data field let transaction = new TestTransaction({ nonce: 0, From 847c733104da96427fed341ec8d1adfec40b4d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 30 Jan 2023 19:56:47 +0200 Subject: [PATCH 056/118] Test for shorter mnemonics (12 words). --- src-wallet/testutils/wallets.ts | 1 + src-wallet/users.spec.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src-wallet/testutils/wallets.ts b/src-wallet/testutils/wallets.ts index 353c777d5..dd73701a0 100644 --- a/src-wallet/testutils/wallets.ts +++ b/src-wallet/testutils/wallets.ts @@ -5,6 +5,7 @@ import { readTestFile } from "./files"; export const DummyPassword = "password"; export const DummyMnemonic = "moral volcano peasant pass circle pen over picture flat shop clap goat never lyrics gather prepare woman film husband gravity behind test tiger improve"; +export const DummyMnemonicOf12Words = "matter trumpet twenty parade fame north lift sail valve salon foster cinnamon"; export async function loadTestWallet(name: string): Promise { let testdataPath = path.resolve(__dirname, "..", "testdata"); diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 8dcf00b69..a167d635c 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -4,7 +4,7 @@ import { ErrInvariantFailed } from "./errors"; import { Mnemonic } from "./mnemonic"; import { TestMessage } from "./testutils/message"; import { TestTransaction } from "./testutils/transaction"; -import { DummyMnemonic, DummyPassword, loadTestWallet, TestWallet } from "./testutils/wallets"; +import { DummyMnemonic, DummyMnemonicOf12Words, DummyPassword, loadTestWallet, TestWallet } from "./testutils/wallets"; import { UserAddress } from "./userAddress"; import { UserSecretKey } from "./userKeys"; import { UserSigner } from "./userSigner"; @@ -35,6 +35,14 @@ describe("test user wallets", () => { assert.equal(mnemonic.deriveKey(2).hex(), carol.secretKeyHex); }); + it("should derive keys (12 words)", async () => { + const mnemonic = Mnemonic.fromString(DummyMnemonicOf12Words); + + assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), "erd1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqg4g7na"); + assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), "erd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqnts4zs09p"); + assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), "erd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksptewtj"); + }); + it("should create secret key", () => { let keyHex = alice.secretKeyHex; let fromBuffer = new UserSecretKey(Buffer.from(keyHex, "hex")); From baf215e8a7c58c7de36fec311c222d0fe63e4f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 31 Jan 2023 00:54:21 +0200 Subject: [PATCH 057/118] Add version checks, add unit tests. --- src-wallet/crypto/encrypt.spec.ts | 8 +++- src-wallet/crypto/encryptor.ts | 5 +++ src-wallet/userWallet.ts | 35 +++++++++------ src-wallet/users.spec.ts | 71 ++++++++++++++++++++++++++++++- 4 files changed, 102 insertions(+), 17 deletions(-) diff --git a/src-wallet/crypto/encrypt.spec.ts b/src-wallet/crypto/encrypt.spec.ts index 4c5f1c517..e09ef6179 100644 --- a/src-wallet/crypto/encrypt.spec.ts +++ b/src-wallet/crypto/encrypt.spec.ts @@ -6,7 +6,7 @@ import { Encryptor } from "./encryptor"; describe("test address", () => { it("encrypts/decrypts", () => { const sensitiveData = Buffer.from("my mnemonic"); - const encryptedData = Encryptor.encrypt(7, sensitiveData, "password123"); + const encryptedData = Encryptor.encrypt(4, sensitiveData, "password123"); const decryptedBuffer = Decryptor.decrypt(encryptedData, "password123"); assert.equal(sensitiveData.toString('hex'), decryptedBuffer.toString('hex')); @@ -14,9 +14,13 @@ describe("test address", () => { it("encodes/decodes kdfparams", () => { const sensitiveData = Buffer.from("my mnemonic"); - const encryptedData = Encryptor.encrypt(7, sensitiveData, "password123"); + const encryptedData = Encryptor.encrypt(4, sensitiveData, "password123"); const decodedData = EncryptedData.fromJSON(encryptedData.toJSON()); assert.deepEqual(decodedData, encryptedData, "invalid decoded data"); }); + + it("fails for bad version", () => { + assert.throws(() => Encryptor.encrypt(5, Buffer.from(""), ""), "Encryptor: unsupported version 5"); + }); }); diff --git a/src-wallet/crypto/encryptor.ts b/src-wallet/crypto/encryptor.ts index c0708acfe..157ca1657 100644 --- a/src-wallet/crypto/encryptor.ts +++ b/src-wallet/crypto/encryptor.ts @@ -1,4 +1,5 @@ import crypto from "crypto"; +import { Err } from "../errors"; import { CipherAlgorithm, DigestAlgorithm, KeyDerivationFunction } from "./constants"; import { ScryptKeyDerivationParams } from "./derivationParams"; import { EncryptedData } from "./encryptedData"; @@ -10,6 +11,10 @@ export enum EncryptorVersion { export class Encryptor { static encrypt(version: EncryptorVersion, data: Buffer, password: string, randomness: Randomness = new Randomness()): EncryptedData { + if (version != EncryptorVersion.V4) { + throw new Err(`Encryptor: unsupported version ${version}`); + } + const kdParams = new ScryptKeyDerivationParams(); const derivedKey = kdParams.generateDerivedKey(Buffer.from(password), randomness.salt); const derivedKeyFirstHalf = derivedKey.slice(0, 16); diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index f9866a1aa..2eb13f1f4 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -58,6 +58,8 @@ export class UserWallet { encryptorVersion = encryptorVersion || EncryptorVersion.V4; randomness = randomness || new Randomness(); + requireVersion(envelopeVersion, [EnvelopeVersion.V4, EnvelopeVersion.V5]); + const publicKey = secretKey.generatePublicKey(); const text = Buffer.concat([secretKey.valueOf(), publicKey.valueOf()]); const encryptedData = Encryptor.encrypt(encryptorVersion, text, password, randomness); @@ -87,6 +89,8 @@ export class UserWallet { encryptorVersion = encryptorVersion || EncryptorVersion.V4; randomness = randomness || new Randomness(); + requireVersion(envelopeVersion, [EnvelopeVersion.V5]); + const encryptedData = Encryptor.encrypt(encryptorVersion, Buffer.from(mnemonic), password, randomness); return new UserWallet({ @@ -113,6 +117,8 @@ export class UserWallet { encryptorVersion = encryptorVersion || EncryptorVersion.V4; randomness = randomness || new Randomness(); + requireVersion(envelopeVersion, [EnvelopeVersion.V5]); + const encryptedData = Encryptor.encrypt(encryptorVersion, arbitraryData, password, randomness); return new UserWallet({ @@ -133,8 +139,10 @@ export class UserWallet { * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { + requireVersion(keyFileObject.version, [EnvelopeVersion.V4, EnvelopeVersion.V5]); + if (keyFileObject.version >= EnvelopeVersion.V5) { - this.requireKind(keyFileObject.kind, UserWalletKind.SecretKey, "decryptSecretKey") + requireKind(keyFileObject.kind, UserWalletKind.SecretKey) } const encryptedData = UserWallet.edFromJSON(keyFileObject); @@ -150,8 +158,8 @@ export class UserWallet { } static decryptMnemonic(keyFileObject: any, password: string): string { - this.requireV5OrHigher(keyFileObject.version, "decryptMnemonic"); - this.requireKind(keyFileObject.kind, UserWalletKind.Mnemonic, "decryptMnemonic") + requireVersion(keyFileObject.version, [EnvelopeVersion.V5]); + requireKind(keyFileObject.kind, UserWalletKind.Mnemonic) const encryptedData = UserWallet.edFromJSON(keyFileObject); const text = Decryptor.decrypt(encryptedData, password); @@ -159,8 +167,8 @@ export class UserWallet { } static decryptArbitrary(keyFileObject: any, password: string): Buffer { - this.requireV5OrHigher(keyFileObject.version, "decryptArbitrary"); - this.requireKind(keyFileObject.kind, UserWalletKind.Arbitrary, "decryptArbitrary") + requireVersion(keyFileObject.version, [EnvelopeVersion.V5]); + requireKind(keyFileObject.kind, UserWalletKind.Arbitrary) const encryptedData = UserWallet.edFromJSON(keyFileObject); const data = Decryptor.decrypt(encryptedData, password); @@ -254,16 +262,17 @@ export class UserWallet { crypto: cryptoSection }; } +} - private static requireKind(kind: UserWalletKind, expectedKind: UserWalletKind, context: string) { - if (kind != expectedKind) { - throw new Err(`Expected kind to be ${expectedKind}, but it was ${kind}. Context: ${context}`); - } +function requireKind(kind: UserWalletKind, expectedKind: UserWalletKind) { + if (kind != expectedKind) { + throw new Err(`Expected kind to be ${expectedKind}, but it was ${kind}.`); } +} - private static requireV5OrHigher(version: EnvelopeVersion, context: string) { - if (version < EnvelopeVersion.V5) { - throw new Err(`Unsupported version: ${version}. Context: ${context}`); - } +function requireVersion(version: EnvelopeVersion, allowedVersions: EnvelopeVersion[]) { + const isAllowed = allowedVersions.includes(version); + if (!isAllowed) { + throw new Err(`Envelope version must be one of: [${allowedVersions.join(", ")}].`); } } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index a167d635c..1d9956ea9 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -9,7 +9,7 @@ import { UserAddress } from "./userAddress"; import { UserSecretKey } from "./userKeys"; import { UserSigner } from "./userSigner"; import { UserVerifier } from "./userVerifier"; -import { UserWallet } from "./userWallet"; +import { EnvelopeVersion, UserWallet } from "./userWallet"; describe("test user wallets", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; @@ -79,7 +79,7 @@ describe("test user wallets", () => { assert.equal(UserSecretKey.fromPem(carol.pemFileText).hex(), carol.secretKeyHex); }); - it("should create and load encrypted files", function () { + it("should create and load keystore files (with secret keys)", function () { this.timeout(10000); let aliceSecretKey = UserSecretKey.fromString(alice.secretKeyHex); @@ -139,6 +139,73 @@ describe("test user wallets", () => { assert.deepEqual(carolKeyFile.toJSON(), carol.keyFileObject); }); + it("should create and load keystore files both as V4 and V5", function () { + this.timeout(10000); + + const aliceSecretKey = UserSecretKey.fromString(alice.secretKeyHex); + const walletV4 = UserWallet.fromSecretKey({ secretKey: aliceSecretKey, password: password }); + const walletV5 = UserWallet.fromSecretKey({ envelopeVersion: EnvelopeVersion.V5, secretKey: aliceSecretKey, password: password }); + const jsonV4 = walletV4.toJSON(); + const jsonV5 = walletV5.toJSON(); + + assert.equal(jsonV4.version, 4); + assert.isUndefined(jsonV4.kind); + assert.equal(jsonV4.bech32, alice.address.bech32()); + + assert.equal(jsonV5.version, 5); + assert.equal(jsonV5.kind, "secretKey"); + assert.equal(jsonV5.bech32, alice.address.bech32()); + + const secretKeyV4 = UserWallet.decryptSecretKey(jsonV4, password); + const secretKeyV5 = UserWallet.decryptSecretKey(jsonV5, password); + assert.equal(secretKeyV4.hex(), alice.secretKeyHex); + assert.equal(secretKeyV5.hex(), alice.secretKeyHex); + }); + + it("should create and load keystore files (with mnemonics)", function () { + this.timeout(10000); + + const wallet = UserWallet.fromMnemonic({ mnemonic: DummyMnemonic, password: password }); + const json = wallet.toJSON(); + + assert.equal(json.version, 5); + assert.equal(json.kind, "mnemonic"); + assert.isUndefined(json.bech32); + + const mnemonicText = UserWallet.decryptMnemonic(json, password); + const mnemonic = Mnemonic.fromString(mnemonicText); + + assert.equal(mnemonicText, DummyMnemonic); + assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), alice.address.bech32()); + assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), bob.address.bech32()); + assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), carol.address.bech32()); + }); + + it("should create and load keystore files (with arbitrary data)", function () { + const data = Buffer.from("hello"); + const wallet = UserWallet.fromArbitrary({ arbitraryData: data, password: password }); + const json = wallet.toJSON(); + + assert.equal(json.version, 5); + assert.equal(json.kind, "arbitrary"); + assert.isUndefined(json.bech32); + + const decryptedData = UserWallet.decryptArbitrary(json, password); + assert.deepEqual(decryptedData, data); + }); + + it("should fail if using bad versions", function () { + const aliceSecretKey = UserSecretKey.fromString(alice.secretKeyHex); + + assert.throws(() => UserWallet.fromSecretKey({ envelopeVersion: 7, secretKey: aliceSecretKey, password: password }), "Envelope version must be one of: [4, 5]."); + assert.throws(() => UserWallet.fromMnemonic({ envelopeVersion: 4, mnemonic: DummyMnemonic, password: password }), "Envelope version must be one of: [5]."); + assert.throws(() => UserWallet.fromArbitrary({ envelopeVersion: 4, arbitraryData: Buffer.from(""), password: password }), "Envelope version must be one of: [5]."); + + assert.throws(() => UserWallet.decryptSecretKey({ version: 3, crypto: {} }, password), "Envelope version must be one of: [4, 5]."); + assert.throws(() => UserWallet.decryptMnemonic({ version: 6, crypto: {} }, password), "Envelope version must be one of: [5]."); + assert.throws(() => UserWallet.decryptArbitrary({ version: 7, crypto: {} }, password), "Envelope version must be one of: [5]."); + }); + it("should sign transactions", async () => { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); From 6f1602759169de2b7cc3178924e0aa9c85f6550a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 1 Feb 2023 22:27:01 +0200 Subject: [PATCH 058/118] Fix after review (point 4). --- src-wallet/userWallet.ts | 44 +++------------------------------------- src-wallet/users.spec.ts | 19 +++-------------- 2 files changed, 6 insertions(+), 57 deletions(-) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 2eb13f1f4..7db5f1046 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -14,8 +14,7 @@ export enum EnvelopeVersion { export enum UserWalletKind { SecretKey = "secretKey", - Mnemonic = "mnemonic", - Arbitrary = "arbitrary" + Mnemonic = "mnemonic" } export class UserWallet { @@ -100,34 +99,6 @@ export class UserWallet { }); } - static fromArbitrary({ - envelopeVersion, - encryptorVersion, - arbitraryData, - password, - randomness, - }: { - envelopeVersion?: EnvelopeVersion; - encryptorVersion?: EncryptorVersion; - arbitraryData: Buffer; - password: string; - randomness?: Randomness; - }): UserWallet { - envelopeVersion = envelopeVersion || EnvelopeVersion.V5; - encryptorVersion = encryptorVersion || EncryptorVersion.V4; - randomness = randomness || new Randomness(); - - requireVersion(envelopeVersion, [EnvelopeVersion.V5]); - - const encryptedData = Encryptor.encrypt(encryptorVersion, arbitraryData, password, randomness); - - return new UserWallet({ - envelopeVersion: envelopeVersion, - kind: UserWalletKind.Arbitrary, - encryptedData - }); - } - /** * Copied from: https://github.com/multiversx/mx-deprecated-core-js/blob/v1.28.0/src/account.js#L42 * Notes: adjustements (code refactoring, no change in logic), in terms of: @@ -166,15 +137,6 @@ export class UserWallet { return text.toString(); } - static decryptArbitrary(keyFileObject: any, password: string): Buffer { - requireVersion(keyFileObject.version, [EnvelopeVersion.V5]); - requireKind(keyFileObject.kind, UserWalletKind.Arbitrary) - - const encryptedData = UserWallet.edFromJSON(keyFileObject); - const data = Decryptor.decrypt(encryptedData, password); - return data; - } - static edFromJSON(keyfileObject: any): EncryptedData { const encryptorVersion: number = (keyfileObject.version == EnvelopeVersion.V4) ? // In V4, the "crypto" section inherits the version from the envelope. @@ -208,7 +170,7 @@ export class UserWallet { return this.getEnvelopeWhenKindIsSecretKey(); } - return this.getEnvelopeWhenKindIsMnemonicOrArbitrary(); + return this.getEnvelopeWhenKindIsMnemonic(); } getEnvelopeWhenKindIsSecretKey(): any { @@ -252,7 +214,7 @@ export class UserWallet { return cryptoSection; } - getEnvelopeWhenKindIsMnemonicOrArbitrary(): any { + getEnvelopeWhenKindIsMnemonic(): any { const cryptoSection = this.getCryptoSectionAsJSON(); return { diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 1d9956ea9..e3128f26c 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -162,7 +162,7 @@ describe("test user wallets", () => { assert.equal(secretKeyV5.hex(), alice.secretKeyHex); }); - it("should create and load keystore files (with mnemonics)", function () { + it.only("should create and load keystore files (with mnemonics)", function () { this.timeout(10000); const wallet = UserWallet.fromMnemonic({ mnemonic: DummyMnemonic, password: password }); @@ -172,6 +172,8 @@ describe("test user wallets", () => { assert.equal(json.kind, "mnemonic"); assert.isUndefined(json.bech32); + console.log(JSON.stringify(json, null, 4)); + const mnemonicText = UserWallet.decryptMnemonic(json, password); const mnemonic = Mnemonic.fromString(mnemonicText); @@ -181,29 +183,14 @@ describe("test user wallets", () => { assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), carol.address.bech32()); }); - it("should create and load keystore files (with arbitrary data)", function () { - const data = Buffer.from("hello"); - const wallet = UserWallet.fromArbitrary({ arbitraryData: data, password: password }); - const json = wallet.toJSON(); - - assert.equal(json.version, 5); - assert.equal(json.kind, "arbitrary"); - assert.isUndefined(json.bech32); - - const decryptedData = UserWallet.decryptArbitrary(json, password); - assert.deepEqual(decryptedData, data); - }); - it("should fail if using bad versions", function () { const aliceSecretKey = UserSecretKey.fromString(alice.secretKeyHex); assert.throws(() => UserWallet.fromSecretKey({ envelopeVersion: 7, secretKey: aliceSecretKey, password: password }), "Envelope version must be one of: [4, 5]."); assert.throws(() => UserWallet.fromMnemonic({ envelopeVersion: 4, mnemonic: DummyMnemonic, password: password }), "Envelope version must be one of: [5]."); - assert.throws(() => UserWallet.fromArbitrary({ envelopeVersion: 4, arbitraryData: Buffer.from(""), password: password }), "Envelope version must be one of: [5]."); assert.throws(() => UserWallet.decryptSecretKey({ version: 3, crypto: {} }, password), "Envelope version must be one of: [4, 5]."); assert.throws(() => UserWallet.decryptMnemonic({ version: 6, crypto: {} }, password), "Envelope version must be one of: [5]."); - assert.throws(() => UserWallet.decryptArbitrary({ version: 7, crypto: {} }, password), "Envelope version must be one of: [5]."); }); it("should sign transactions", async () => { From 1577e466b0cdea96c534dfe1434440c0f24c423c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 2 Feb 2023 20:38:14 +0200 Subject: [PATCH 059/118] Revert version-related changes. Keystore version remains v4. --- src-wallet/crypto/encrypt.spec.ts | 8 +- src-wallet/crypto/encryptor.ts | 9 +- src-wallet/testdata/alice.json | 1 + src-wallet/testdata/bob.json | 1 + src-wallet/testdata/carol.json | 1 + src-wallet/testdata/withDummyMnemonic.json | 21 +++++ src-wallet/testdata/withoutKind.json | 22 +++++ src-wallet/testutils/wallets.ts | 26 ++++-- src-wallet/userWallet.ts | 99 ++++------------------ src-wallet/users.spec.ts | 54 +++++------- 10 files changed, 105 insertions(+), 137 deletions(-) create mode 100644 src-wallet/testdata/withDummyMnemonic.json create mode 100644 src-wallet/testdata/withoutKind.json diff --git a/src-wallet/crypto/encrypt.spec.ts b/src-wallet/crypto/encrypt.spec.ts index e09ef6179..1f785968d 100644 --- a/src-wallet/crypto/encrypt.spec.ts +++ b/src-wallet/crypto/encrypt.spec.ts @@ -6,7 +6,7 @@ import { Encryptor } from "./encryptor"; describe("test address", () => { it("encrypts/decrypts", () => { const sensitiveData = Buffer.from("my mnemonic"); - const encryptedData = Encryptor.encrypt(4, sensitiveData, "password123"); + const encryptedData = Encryptor.encrypt(sensitiveData, "password123"); const decryptedBuffer = Decryptor.decrypt(encryptedData, "password123"); assert.equal(sensitiveData.toString('hex'), decryptedBuffer.toString('hex')); @@ -14,13 +14,9 @@ describe("test address", () => { it("encodes/decodes kdfparams", () => { const sensitiveData = Buffer.from("my mnemonic"); - const encryptedData = Encryptor.encrypt(4, sensitiveData, "password123"); + const encryptedData = Encryptor.encrypt(sensitiveData, "password123"); const decodedData = EncryptedData.fromJSON(encryptedData.toJSON()); assert.deepEqual(decodedData, encryptedData, "invalid decoded data"); }); - - it("fails for bad version", () => { - assert.throws(() => Encryptor.encrypt(5, Buffer.from(""), ""), "Encryptor: unsupported version 5"); - }); }); diff --git a/src-wallet/crypto/encryptor.ts b/src-wallet/crypto/encryptor.ts index 157ca1657..a7e357f2e 100644 --- a/src-wallet/crypto/encryptor.ts +++ b/src-wallet/crypto/encryptor.ts @@ -1,5 +1,4 @@ import crypto from "crypto"; -import { Err } from "../errors"; import { CipherAlgorithm, DigestAlgorithm, KeyDerivationFunction } from "./constants"; import { ScryptKeyDerivationParams } from "./derivationParams"; import { EncryptedData } from "./encryptedData"; @@ -10,11 +9,7 @@ export enum EncryptorVersion { } export class Encryptor { - static encrypt(version: EncryptorVersion, data: Buffer, password: string, randomness: Randomness = new Randomness()): EncryptedData { - if (version != EncryptorVersion.V4) { - throw new Err(`Encryptor: unsupported version ${version}`); - } - + static encrypt(data: Buffer, password: string, randomness: Randomness = new Randomness()): EncryptedData { const kdParams = new ScryptKeyDerivationParams(); const derivedKey = kdParams.generateDerivedKey(Buffer.from(password), randomness.salt); const derivedKeyFirstHalf = derivedKey.slice(0, 16); @@ -25,7 +20,7 @@ export class Encryptor { const mac = crypto.createHmac(DigestAlgorithm, derivedKeySecondHalf).update(ciphertext).digest(); return new EncryptedData({ - version: version, + version: EncryptorVersion.V4, id: randomness.id, ciphertext: ciphertext.toString('hex'), iv: randomness.iv.toString('hex'), diff --git a/src-wallet/testdata/alice.json b/src-wallet/testdata/alice.json index 9e83170cf..18c4ea1e9 100644 --- a/src-wallet/testdata/alice.json +++ b/src-wallet/testdata/alice.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "0dc10c02-b59b-4bac-9710-6b2cfa4284ba", "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", diff --git a/src-wallet/testdata/bob.json b/src-wallet/testdata/bob.json index 439b394a5..9efb41109 100644 --- a/src-wallet/testdata/bob.json +++ b/src-wallet/testdata/bob.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "85fdc8a7-7119-479d-b7fb-ab4413ed038d", "address": "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", "bech32": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", diff --git a/src-wallet/testdata/carol.json b/src-wallet/testdata/carol.json index 3614a5ba2..1014a8230 100644 --- a/src-wallet/testdata/carol.json +++ b/src-wallet/testdata/carol.json @@ -1,5 +1,6 @@ { "version": 4, + "kind": "secretKey", "id": "65894f35-d142-41d2-9335-6ad02e0ed0be", "address": "b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", "bech32": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", diff --git a/src-wallet/testdata/withDummyMnemonic.json b/src-wallet/testdata/withDummyMnemonic.json new file mode 100644 index 000000000..29a585296 --- /dev/null +++ b/src-wallet/testdata/withDummyMnemonic.json @@ -0,0 +1,21 @@ +{ + "version": 4, + "id": "5b448dbc-5c72-4d83-8038-938b1f8dff19", + "kind": "mnemonic", + "crypto": { + "ciphertext": "6d70fbdceba874f56f15af4b1d060223799288cfc5d276d9ebb91732f5a38c3c59f83896fa7e7eb6a04c05475a6fe4d154de9b9441864c507abd0eb6987dac521b64c0c82783a3cd1e09270cd6cb5ae493f9af694b891253ac1f1ffded68b5ef39c972307e3c33a8354337540908acc795d4df72298dda1ca28ac920983e6a39a01e2bc988bd0b21f864c6de8b5356d11e4b77bc6f75ef", + "cipherparams": { + "iv": "2da5620906634972d9a623bc249d63d4" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "aa9e0ba6b188703071a582c10e5331f2756279feb0e2768f1ba0fd38ec77f035", + "n": 4096, + "r": 8, + "p": 1 + }, + "mac": "5bc1b20b6d903b8ef3273eedf028112d65eaf85a5ef4215917c1209ec2df715a" + } +} diff --git a/src-wallet/testdata/withoutKind.json b/src-wallet/testdata/withoutKind.json new file mode 100644 index 000000000..9e83170cf --- /dev/null +++ b/src-wallet/testdata/withoutKind.json @@ -0,0 +1,22 @@ +{ + "version": 4, + "id": "0dc10c02-b59b-4bac-9710-6b2cfa4284ba", + "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "crypto": { + "ciphertext": "4c41ef6fdfd52c39b1585a875eb3c86d30a315642d0e35bb8205b6372c1882f135441099b11ff76345a6f3a930b5665aaf9f7325a32c8ccd60081c797aa2d538", + "cipherparams": { + "iv": "033182afaa1ebaafcde9ccc68a5eac31" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "4903bd0e7880baa04fc4f886518ac5c672cdc745a6bd13dcec2b6c12e9bffe8d", + "n": 4096, + "r": 8, + "p": 1 + }, + "mac": "5b4a6f14ab74ba7ca23db6847e28447f0e6a7724ba9664cf425df707a84f5a8b" + } +} diff --git a/src-wallet/testutils/wallets.ts b/src-wallet/testutils/wallets.ts index dd73701a0..1ce1af3e5 100644 --- a/src-wallet/testutils/wallets.ts +++ b/src-wallet/testutils/wallets.ts @@ -8,15 +8,25 @@ export const DummyMnemonic = "moral volcano peasant pass circle pen over picture export const DummyMnemonicOf12Words = "matter trumpet twenty parade fame north lift sail valve salon foster cinnamon"; export async function loadTestWallet(name: string): Promise { - let testdataPath = path.resolve(__dirname, "..", "testdata"); - let jsonFilePath = path.resolve(testdataPath, `${name}.json`); - let pemFilePath = path.resolve(testdataPath, `${name}.pem`); + const keystore = await loadTestKeystore(`${name}.json`) + const pemText = await loadTestPemFile(`${name}.pem`) + const pemKey = UserSecretKey.fromPem(pemText); + const address = new UserAddress(Buffer.from(keystore.address, "hex")); - let jsonWallet = JSON.parse(await readTestFile(jsonFilePath)); - let pemText = await readTestFile(pemFilePath); - let pemKey = UserSecretKey.fromPem(pemText); - let address = new UserAddress(Buffer.from(jsonWallet.address, "hex")); - return new TestWallet(address, pemKey.hex(), jsonWallet, pemText); + return new TestWallet(address, pemKey.hex(), keystore, pemText); +} + +export async function loadTestKeystore(file: string): Promise { + const testdataPath = path.resolve(__dirname, "..", "testdata"); + const keystorePath = path.resolve(testdataPath, file); + const json = await readTestFile(keystorePath); + return JSON.parse(json); +} + +export async function loadTestPemFile(file: string): Promise { + const testdataPath = path.resolve(__dirname, "..", "testdata"); + const pemFilePath = path.resolve(testdataPath, file); + return await readTestFile(pemFilePath); } export class TestWallet { diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 7db5f1046..8ba2014f0 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -3,97 +3,55 @@ import { ScryptKeyDerivationParams } from "./crypto/derivationParams"; import { Err } from "./errors"; import { UserPublicKey, UserSecretKey } from "./userKeys"; -export enum EnvelopeVersion { - // Does not have the "kind" field, and is meant to hold the **secret key**. - // The "crypto" section is not versioned. - V4 = 4, - // Has the "kind" field, and is meant to hold the **secret key** or **the mnemonic** (or any other secret payload). - // Furthermore, the "crypto" section is versioned separately. - V5 = 5 -} - export enum UserWalletKind { SecretKey = "secretKey", Mnemonic = "mnemonic" } export class UserWallet { - private readonly envelopeVersion: number; private readonly kind: UserWalletKind; private readonly encryptedData: EncryptedData; private readonly publicKeyWhenKindIsSecretKey?: UserPublicKey; - private constructor({ - envelopeVersion: envelopeVersion, - kind, - encryptedData, - publicKeyWhenKindIsSecretKey - }: { - envelopeVersion: EnvelopeVersion; + private constructor({ kind, encryptedData, publicKeyWhenKindIsSecretKey }: { kind: UserWalletKind; encryptedData: EncryptedData; publicKeyWhenKindIsSecretKey?: UserPublicKey; }) { - this.envelopeVersion = envelopeVersion; this.kind = kind; this.encryptedData = encryptedData; this.publicKeyWhenKindIsSecretKey = publicKeyWhenKindIsSecretKey; } - static fromSecretKey({ - envelopeVersion, - encryptorVersion, - secretKey, - password, - randomness, - }: { - envelopeVersion?: EnvelopeVersion; + static fromSecretKey({ secretKey, password, randomness }: { encryptorVersion?: EncryptorVersion; secretKey: UserSecretKey; password: string; randomness?: Randomness; }): UserWallet { - envelopeVersion = envelopeVersion || EnvelopeVersion.V4; - encryptorVersion = encryptorVersion || EncryptorVersion.V4; randomness = randomness || new Randomness(); - requireVersion(envelopeVersion, [EnvelopeVersion.V4, EnvelopeVersion.V5]); - const publicKey = secretKey.generatePublicKey(); const text = Buffer.concat([secretKey.valueOf(), publicKey.valueOf()]); - const encryptedData = Encryptor.encrypt(encryptorVersion, text, password, randomness); + const encryptedData = Encryptor.encrypt(text, password, randomness); return new UserWallet({ - envelopeVersion: envelopeVersion, kind: UserWalletKind.SecretKey, encryptedData, publicKeyWhenKindIsSecretKey: publicKey }); } - static fromMnemonic({ - envelopeVersion, - encryptorVersion, - mnemonic, - password, - randomness, - }: { - envelopeVersion?: EnvelopeVersion; - encryptorVersion?: EncryptorVersion; + static fromMnemonic({ mnemonic, password, randomness }: { mnemonic: string; password: string; randomness?: Randomness; }): UserWallet { - envelopeVersion = envelopeVersion || EnvelopeVersion.V5; - encryptorVersion = encryptorVersion || EncryptorVersion.V4; randomness = randomness || new Randomness(); - requireVersion(envelopeVersion, [EnvelopeVersion.V5]); - - const encryptedData = Encryptor.encrypt(encryptorVersion, Buffer.from(mnemonic), password, randomness); + const encryptedData = Encryptor.encrypt(Buffer.from(mnemonic), password, randomness); return new UserWallet({ - envelopeVersion: envelopeVersion, kind: UserWalletKind.Mnemonic, encryptedData }); @@ -110,11 +68,7 @@ export class UserWallet { * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { - requireVersion(keyFileObject.version, [EnvelopeVersion.V4, EnvelopeVersion.V5]); - - if (keyFileObject.version >= EnvelopeVersion.V5) { - requireKind(keyFileObject.kind, UserWalletKind.SecretKey) - } + // Here, we do not check the "kind" field. Older keystore files holding secret keys do not have this field. const encryptedData = UserWallet.edFromJSON(keyFileObject); @@ -129,8 +83,9 @@ export class UserWallet { } static decryptMnemonic(keyFileObject: any, password: string): string { - requireVersion(keyFileObject.version, [EnvelopeVersion.V5]); - requireKind(keyFileObject.kind, UserWalletKind.Mnemonic) + if (keyFileObject.kind != UserWalletKind.Mnemonic) { + throw new Err(`Expected kind to be ${UserWalletKind.Mnemonic}, but it was ${keyFileObject.kind}.`); + } const encryptedData = UserWallet.edFromJSON(keyFileObject); const text = Decryptor.decrypt(encryptedData, password); @@ -138,14 +93,8 @@ export class UserWallet { } static edFromJSON(keyfileObject: any): EncryptedData { - const encryptorVersion: number = (keyfileObject.version == EnvelopeVersion.V4) ? - // In V4, the "crypto" section inherits the version from the envelope. - EncryptorVersion.V4 : - // In V5, the "crypto" section has its own version. - keyfileObject.crypto.version; - return new EncryptedData({ - version: encryptorVersion, + version: keyfileObject.version, id: keyfileObject.id, cipher: keyfileObject.crypto.cipher, ciphertext: keyfileObject.crypto.ciphertext, @@ -167,13 +116,13 @@ export class UserWallet { */ toJSON(): any { if (this.kind == UserWalletKind.SecretKey) { - return this.getEnvelopeWhenKindIsSecretKey(); + return this.toJSONWhenKindIsSecretKey(); } - return this.getEnvelopeWhenKindIsMnemonic(); + return this.toJSONWhenKindIsMnemonic(); } - getEnvelopeWhenKindIsSecretKey(): any { + private toJSONWhenKindIsSecretKey(): any { if (!this.publicKeyWhenKindIsSecretKey) { throw new Err("Public key isn't available"); } @@ -181,9 +130,9 @@ export class UserWallet { const cryptoSection = this.getCryptoSectionAsJSON(); const envelope: any = { - version: this.envelopeVersion, + version: this.encryptedData.version, // Adding "kind", if appropriate. - ...(this.envelopeVersion >= 5 ? { kind: UserWalletKind.SecretKey } : {}), + ...(this.kind ? { kind: this.kind } : {}), id: this.encryptedData.id, address: this.publicKeyWhenKindIsSecretKey.hex(), bech32: this.publicKeyWhenKindIsSecretKey.toAddress().toString(), @@ -195,8 +144,6 @@ export class UserWallet { getCryptoSectionAsJSON(): any { const cryptoSection: any = { - // Adding "version", if appropriate. - ...(this.envelopeVersion >= 5 ? { version: this.encryptedData.version } : {}), ciphertext: this.encryptedData.ciphertext, cipherparams: { iv: this.encryptedData.iv }, cipher: CipherAlgorithm, @@ -214,11 +161,11 @@ export class UserWallet { return cryptoSection; } - getEnvelopeWhenKindIsMnemonic(): any { + toJSONWhenKindIsMnemonic(): any { const cryptoSection = this.getCryptoSectionAsJSON(); return { - version: this.envelopeVersion, + version: this.encryptedData.version, id: this.encryptedData.id, kind: this.kind, crypto: cryptoSection @@ -226,15 +173,3 @@ export class UserWallet { } } -function requireKind(kind: UserWalletKind, expectedKind: UserWalletKind) { - if (kind != expectedKind) { - throw new Err(`Expected kind to be ${expectedKind}, but it was ${kind}.`); - } -} - -function requireVersion(version: EnvelopeVersion, allowedVersions: EnvelopeVersion[]) { - const isAllowed = allowedVersions.includes(version); - if (!isAllowed) { - throw new Err(`Envelope version must be one of: [${allowedVersions.join(", ")}].`); - } -} diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index e3128f26c..faf6535a2 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -4,12 +4,12 @@ import { ErrInvariantFailed } from "./errors"; import { Mnemonic } from "./mnemonic"; import { TestMessage } from "./testutils/message"; import { TestTransaction } from "./testutils/transaction"; -import { DummyMnemonic, DummyMnemonicOf12Words, DummyPassword, loadTestWallet, TestWallet } from "./testutils/wallets"; +import { DummyMnemonic, DummyMnemonicOf12Words, DummyPassword, loadTestKeystore, loadTestWallet, TestWallet } from "./testutils/wallets"; import { UserAddress } from "./userAddress"; import { UserSecretKey } from "./userKeys"; import { UserSigner } from "./userSigner"; import { UserVerifier } from "./userVerifier"; -import { EnvelopeVersion, UserWallet } from "./userWallet"; +import { UserWallet } from "./userWallet"; describe("test user wallets", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; @@ -139,41 +139,23 @@ describe("test user wallets", () => { assert.deepEqual(carolKeyFile.toJSON(), carol.keyFileObject); }); - it("should create and load keystore files both as V4 and V5", function () { - this.timeout(10000); - - const aliceSecretKey = UserSecretKey.fromString(alice.secretKeyHex); - const walletV4 = UserWallet.fromSecretKey({ secretKey: aliceSecretKey, password: password }); - const walletV5 = UserWallet.fromSecretKey({ envelopeVersion: EnvelopeVersion.V5, secretKey: aliceSecretKey, password: password }); - const jsonV4 = walletV4.toJSON(); - const jsonV5 = walletV5.toJSON(); - - assert.equal(jsonV4.version, 4); - assert.isUndefined(jsonV4.kind); - assert.equal(jsonV4.bech32, alice.address.bech32()); + it("should load keystore files (with secret keys, but without 'kind' field)", async function () { + const keyFileObject = await loadTestKeystore("withoutKind.json"); + const secretKey = UserWallet.decryptSecretKey(keyFileObject, password); - assert.equal(jsonV5.version, 5); - assert.equal(jsonV5.kind, "secretKey"); - assert.equal(jsonV5.bech32, alice.address.bech32()); - - const secretKeyV4 = UserWallet.decryptSecretKey(jsonV4, password); - const secretKeyV5 = UserWallet.decryptSecretKey(jsonV5, password); - assert.equal(secretKeyV4.hex(), alice.secretKeyHex); - assert.equal(secretKeyV5.hex(), alice.secretKeyHex); + assert.equal(secretKey.generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); }); - it.only("should create and load keystore files (with mnemonics)", function () { + it("should create and load keystore files (with mnemonics)", async function () { this.timeout(10000); const wallet = UserWallet.fromMnemonic({ mnemonic: DummyMnemonic, password: password }); const json = wallet.toJSON(); - assert.equal(json.version, 5); + assert.equal(json.version, 4); assert.equal(json.kind, "mnemonic"); assert.isUndefined(json.bech32); - console.log(JSON.stringify(json, null, 4)); - const mnemonicText = UserWallet.decryptMnemonic(json, password); const mnemonic = Mnemonic.fromString(mnemonicText); @@ -181,16 +163,20 @@ describe("test user wallets", () => { assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), alice.address.bech32()); assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), bob.address.bech32()); assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), carol.address.bech32()); - }); - it("should fail if using bad versions", function () { - const aliceSecretKey = UserSecretKey.fromString(alice.secretKeyHex); - - assert.throws(() => UserWallet.fromSecretKey({ envelopeVersion: 7, secretKey: aliceSecretKey, password: password }), "Envelope version must be one of: [4, 5]."); - assert.throws(() => UserWallet.fromMnemonic({ envelopeVersion: 4, mnemonic: DummyMnemonic, password: password }), "Envelope version must be one of: [5]."); + // With provided randomness, in order to reproduce our test wallets + const expectedDummyWallet = await loadTestKeystore("withDummyMnemonic.json"); + const dummyWallet = UserWallet.fromMnemonic({ + mnemonic: DummyMnemonic, + password: password, + randomness: new Randomness({ + id: "5b448dbc-5c72-4d83-8038-938b1f8dff19", + iv: Buffer.from("2da5620906634972d9a623bc249d63d4", "hex"), + salt: Buffer.from("aa9e0ba6b188703071a582c10e5331f2756279feb0e2768f1ba0fd38ec77f035", "hex") + }) + }); - assert.throws(() => UserWallet.decryptSecretKey({ version: 3, crypto: {} }, password), "Envelope version must be one of: [4, 5]."); - assert.throws(() => UserWallet.decryptMnemonic({ version: 6, crypto: {} }, password), "Envelope version must be one of: [5]."); + assert.deepEqual(dummyWallet.toJSON(), expectedDummyWallet); }); it("should sign transactions", async () => { From 645a218bb27d03d82035fb914b80b096d64e6d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 2 Feb 2023 20:52:48 +0200 Subject: [PATCH 060/118] Extra type safety. --- src-wallet/mnemonic.ts | 8 ++++---- src-wallet/userWallet.ts | 9 ++++++--- src-wallet/users.spec.ts | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts index b9dba56ae..ed5423b26 100644 --- a/src-wallet/mnemonic.ts +++ b/src-wallet/mnemonic.ts @@ -1,7 +1,7 @@ -import { generateMnemonic, validateMnemonic, mnemonicToSeedSync } from "bip39"; -import { UserSecretKey } from "./userKeys"; +import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "bip39"; import { derivePath } from "ed25519-hd-key"; import { ErrWrongMnemonic } from "./errors"; +import { UserSecretKey } from "./userKeys"; const MNEMONIC_STRENGTH = 256; const BIP44_DERIVATION_PREFIX = "m/44'/508'/0'/0'"; @@ -20,12 +20,12 @@ export class Mnemonic { static fromString(text: string) { text = text.trim(); - + Mnemonic.assertTextIsValid(text); return new Mnemonic(text); } - private static assertTextIsValid(text: string) { + public static assertTextIsValid(text: string) { let isValid = validateMnemonic(text); if (!isValid) { diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 8ba2014f0..5b6f57706 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -1,6 +1,7 @@ import { CipherAlgorithm, Decryptor, EncryptedData, Encryptor, EncryptorVersion, KeyDerivationFunction, Randomness } from "./crypto"; import { ScryptKeyDerivationParams } from "./crypto/derivationParams"; import { Err } from "./errors"; +import { Mnemonic } from "./mnemonic"; import { UserPublicKey, UserSecretKey } from "./userKeys"; export enum UserWalletKind { @@ -49,6 +50,7 @@ export class UserWallet { }): UserWallet { randomness = randomness || new Randomness(); + Mnemonic.assertTextIsValid(mnemonic); const encryptedData = Encryptor.encrypt(Buffer.from(mnemonic), password, randomness); return new UserWallet({ @@ -82,14 +84,15 @@ export class UserWallet { return new UserSecretKey(seed); } - static decryptMnemonic(keyFileObject: any, password: string): string { + static decryptMnemonic(keyFileObject: any, password: string): Mnemonic { if (keyFileObject.kind != UserWalletKind.Mnemonic) { throw new Err(`Expected kind to be ${UserWalletKind.Mnemonic}, but it was ${keyFileObject.kind}.`); } const encryptedData = UserWallet.edFromJSON(keyFileObject); - const text = Decryptor.decrypt(encryptedData, password); - return text.toString(); + const data = Decryptor.decrypt(encryptedData, password); + const mnemonic = Mnemonic.fromString(data.toString()) + return mnemonic; } static edFromJSON(keyfileObject: any): EncryptedData { diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index faf6535a2..98a1a2d85 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -156,8 +156,8 @@ describe("test user wallets", () => { assert.equal(json.kind, "mnemonic"); assert.isUndefined(json.bech32); - const mnemonicText = UserWallet.decryptMnemonic(json, password); - const mnemonic = Mnemonic.fromString(mnemonicText); + const mnemonic = UserWallet.decryptMnemonic(json, password); + const mnemonicText = mnemonic.toString(); assert.equal(mnemonicText, DummyMnemonic); assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), alice.address.bech32()); From ec013a0e79877e7f620e4db002688ec87c3f2e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 2 Feb 2023 21:04:43 +0200 Subject: [PATCH 061/118] Fix after self-review. --- src-wallet/crypto/encryptor.ts | 8 +++++++- src-wallet/userWallet.ts | 25 +++++++++++++++---------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src-wallet/crypto/encryptor.ts b/src-wallet/crypto/encryptor.ts index a7e357f2e..9d6e33c1b 100644 --- a/src-wallet/crypto/encryptor.ts +++ b/src-wallet/crypto/encryptor.ts @@ -4,12 +4,18 @@ 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: Randomness = new Randomness()): EncryptedData { + 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); diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 5b6f57706..8c6e36681 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -1,9 +1,15 @@ -import { CipherAlgorithm, Decryptor, EncryptedData, Encryptor, EncryptorVersion, KeyDerivationFunction, Randomness } from "./crypto"; +import { CipherAlgorithm, Decryptor, EncryptedData, Encryptor, KeyDerivationFunction, Randomness } from "./crypto"; import { ScryptKeyDerivationParams } from "./crypto/derivationParams"; import { Err } from "./errors"; import { Mnemonic } from "./mnemonic"; import { UserPublicKey, UserSecretKey } from "./userKeys"; +interface IRandomness { + id: string; + iv: Buffer; + salt: Buffer; +} + export enum UserWalletKind { SecretKey = "secretKey", Mnemonic = "mnemonic" @@ -25,16 +31,15 @@ export class UserWallet { } static fromSecretKey({ secretKey, password, randomness }: { - encryptorVersion?: EncryptorVersion; secretKey: UserSecretKey; password: string; - randomness?: Randomness; + randomness?: IRandomness; }): UserWallet { randomness = randomness || new Randomness(); const publicKey = secretKey.generatePublicKey(); - const text = Buffer.concat([secretKey.valueOf(), publicKey.valueOf()]); - const encryptedData = Encryptor.encrypt(text, password, randomness); + const data = Buffer.concat([secretKey.valueOf(), publicKey.valueOf()]); + const encryptedData = Encryptor.encrypt(data, password, randomness); return new UserWallet({ kind: UserWalletKind.SecretKey, @@ -46,12 +51,13 @@ export class UserWallet { static fromMnemonic({ mnemonic, password, randomness }: { mnemonic: string; password: string; - randomness?: Randomness; + randomness?: IRandomness; }): UserWallet { randomness = randomness || new Randomness(); Mnemonic.assertTextIsValid(mnemonic); - const encryptedData = Encryptor.encrypt(Buffer.from(mnemonic), password, randomness); + const data = Buffer.from(mnemonic); + const encryptedData = Encryptor.encrypt(data, password, randomness); return new UserWallet({ kind: UserWalletKind.Mnemonic, @@ -70,7 +76,7 @@ export class UserWallet { * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { - // Here, we do not check the "kind" field. Older keystore files holding secret keys do not have this field. + // Here, we do not check the "kind" field. Older keystore files (holding only secret keys) do not have this field. const encryptedData = UserWallet.edFromJSON(keyFileObject); @@ -134,8 +140,7 @@ export class UserWallet { const envelope: any = { version: this.encryptedData.version, - // Adding "kind", if appropriate. - ...(this.kind ? { kind: this.kind } : {}), + kind: this.kind, id: this.encryptedData.id, address: this.publicKeyWhenKindIsSecretKey.hex(), bech32: this.publicKeyWhenKindIsSecretKey.toAddress().toString(), From 7cdc80ba0416295a4b2cbfb15227e592641e64a4 Mon Sep 17 00:00:00 2001 From: andreibancioiu Date: Wed, 22 Feb 2023 12:10:20 +0200 Subject: [PATCH 062/118] Do not handle user's signature in GuardianSigner (simplification). --- src-wallet/guardianSigner.ts | 10 ---------- src-wallet/interface.ts | 5 ----- 2 files changed, 15 deletions(-) diff --git a/src-wallet/guardianSigner.ts b/src-wallet/guardianSigner.ts index 39d65751a..b6237bb0a 100644 --- a/src-wallet/guardianSigner.ts +++ b/src-wallet/guardianSigner.ts @@ -26,20 +26,10 @@ export class GuardianSigner extends UserSigner { } private tryGuard(signable: ISignable) { - const ownerSignature = signable.getSignature() const bufferToSign = signable.serializeForSigning(); const guardianSignatureBuffer = this.secretKey.sign(bufferToSign); const guardianSignature = new Signature(guardianSignatureBuffer); - this.addOwnerSignature(signable, ownerSignature) - this.doApplySignature(signable, guardianSignature); - } - - protected doApplySignature(signable: ISignable, guardianSignature: ISignature) { signable.applyGuardianSignature(guardianSignature); } - - private addOwnerSignature(signable: ISignable, ownerSignature: ISignature) { - signable.applySignature(ownerSignature); - } } diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts index 99806f228..0cff489e7 100644 --- a/src-wallet/interface.ts +++ b/src-wallet/interface.ts @@ -51,11 +51,6 @@ export interface ISignable { */ serializeForSigning(): Buffer; - /** - * Returns the signature of the sender. - */ - getSignature(): ISignature; - /** * Applies the computed signature on the object itself. * From 7ad21ff4c565e8097a94cfc9cd14bd5c578b73f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 23 Feb 2023 20:15:35 +0200 Subject: [PATCH 063/118] Attempt to avoid breaking change. --- src-wallet/guardianSigner.ts | 10 +++++----- src-wallet/interface.ts | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src-wallet/guardianSigner.ts b/src-wallet/guardianSigner.ts index b6237bb0a..29ba30668 100644 --- a/src-wallet/guardianSigner.ts +++ b/src-wallet/guardianSigner.ts @@ -1,8 +1,8 @@ -import { ISignable, ISignature } from "./interface"; +import { ErrSignerCannotSign } from "./errors"; +import { IGuardableSignable } from "./interface"; +import { Signature } from "./signature"; import { UserSecretKey } from "./userKeys"; import { UserSigner } from "./userSigner"; -import { Signature } from "./signature"; -import { ErrSignerCannotSign } from "./errors"; /** * ed25519 signer @@ -17,7 +17,7 @@ export class GuardianSigner extends UserSigner { * Signs a message. * @param signable the message to be signed (e.g. a {@link Transaction}). */ - async guard(signable: ISignable): Promise { + async guard(signable: IGuardableSignable): Promise { try { this.tryGuard(signable); } catch (err: any) { @@ -25,7 +25,7 @@ export class GuardianSigner extends UserSigner { } } - private tryGuard(signable: ISignable) { + private tryGuard(signable: IGuardableSignable) { const bufferToSign = signable.serializeForSigning(); const guardianSignatureBuffer = this.secretKey.sign(bufferToSign); const guardianSignature = new Signature(guardianSignatureBuffer); diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts index 0cff489e7..d28dfe1fc 100644 --- a/src-wallet/interface.ts +++ b/src-wallet/interface.ts @@ -35,7 +35,7 @@ export interface IGuardianSigner { /** * Signs a message (e.g. a transaction). */ - guard(signable: ISignable): Promise; + guard(signable: IGuardableSignable): Promise; } export interface IVerifier { @@ -57,7 +57,9 @@ export interface ISignable { * @param signature The computed signature */ applySignature(signature: ISignature): void; +} +export interface IGuardableSignable extends ISignable { /** * Applies the guardian signature on the transaction. * From c71d332c6d17e86b5b5ead18f9c11d23d2e38e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 23 Feb 2023 20:45:57 +0200 Subject: [PATCH 064/118] Drop some interfaces. --- src-wallet/interface.ts | 42 +---------------------------- src-wallet/testutils/message.ts | 2 +- src-wallet/testutils/transaction.ts | 2 +- src-wallet/userKeys.ts | 7 +++-- src-wallet/userSigner.ts | 11 ++++---- src-wallet/userVerifier.ts | 14 ++++++---- 6 files changed, 21 insertions(+), 57 deletions(-) diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts index d28dfe1fc..dc3c27740 100644 --- a/src-wallet/interface.ts +++ b/src-wallet/interface.ts @@ -1,47 +1,7 @@ -export interface IAddress { - hex(): string; - bech32(): string; - pubkey(): Buffer; -} - export interface ISignature { hex(): string; } -/** - * An interface that defines a signing-capable object. - */ -export interface ISigner { - /** - * Gets the {@link Address} of the signer. - */ - getAddress(): IAddress; - - /** - * Signs a message (e.g. a transaction). - */ - sign(signable: ISignable): Promise; -} - -/** - * An interface that defines a signing-capable object. - */ -export interface IGuardianSigner { - /** - * Gets the {@link Address} of the signer. - */ - getAddress(): IAddress; - - /** - * Signs a message (e.g. a transaction). - */ - guard(signable: IGuardableSignable): Promise; -} - -export interface IVerifier { - verify(message: IVerifiable): boolean; -} - /** * An interface that defines a signable object (e.g. a transaction). */ @@ -80,5 +40,5 @@ export interface IVerifiable { /** * Returns the signable object in its raw form - a sequence of bytes to be verified. */ - serializeForSigning(signedBy?: IAddress): Buffer; + serializeForSigning(): Buffer; } diff --git a/src-wallet/testutils/message.ts b/src-wallet/testutils/message.ts index 7ed55520d..98c660e34 100644 --- a/src-wallet/testutils/message.ts +++ b/src-wallet/testutils/message.ts @@ -1,4 +1,4 @@ -import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; +import { ISignable, ISignature, IVerifiable } from "../interface"; import { Signature } from "../signature"; /** diff --git a/src-wallet/testutils/transaction.ts b/src-wallet/testutils/transaction.ts index e09ee2194..3a15b3207 100644 --- a/src-wallet/testutils/transaction.ts +++ b/src-wallet/testutils/transaction.ts @@ -1,4 +1,4 @@ -import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; +import { ISignable, ISignature, IVerifiable } from "../interface"; import { Signature } from "../signature"; /** diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index eab6b92f2..3d2c4cbc4 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -1,8 +1,7 @@ import * as tweetnacl from "tweetnacl"; -import { UserAddress } from "./userAddress"; import { guardLength } from "./assertions"; import { parseUserKey } from "./pem"; -import { IAddress } from "./interface"; +import { UserAddress } from "./userAddress"; export const USER_SEED_LENGTH = 32; export const USER_PUBKEY_LENGTH = 32; @@ -57,7 +56,7 @@ export class UserPublicKey { constructor(buffer: Buffer) { guardLength(buffer, USER_PUBKEY_LENGTH); - + this.buffer = buffer; } @@ -76,7 +75,7 @@ export class UserPublicKey { return this.buffer.toString("hex"); } - toAddress(): IAddress { + toAddress(): UserAddress { return new UserAddress(this.buffer); } diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 24f74e07a..b0bb1eecd 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -1,20 +1,21 @@ -import { IAddress, ISignable, ISignature, ISigner } from "./interface"; +import { ErrSignerCannotSign } from "./errors"; +import { ISignable } from "./interface"; import { Signature } from "./signature"; +import { UserAddress } from "./userAddress"; import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; -import { ErrSignerCannotSign } from "./errors"; /** * ed25519 signer */ -export class UserSigner implements ISigner { +export class UserSigner { protected readonly secretKey: UserSecretKey; constructor(secretKey: UserSecretKey) { this.secretKey = secretKey; } - static fromWallet(keyFileObject: any, password: string): ISigner { + static fromWallet(keyFileObject: any, password: string): UserSigner { let secretKey = UserWallet.decryptSecretKey(keyFileObject, password); return new UserSigner(secretKey); } @@ -47,7 +48,7 @@ export class UserSigner implements ISigner { /** * Gets the address of the signer. */ - getAddress(): IAddress { + getAddress(): UserAddress { return this.secretKey.generatePublicKey().toAddress(); } } diff --git a/src-wallet/userVerifier.ts b/src-wallet/userVerifier.ts index 73e8e4f6b..ae747460a 100644 --- a/src-wallet/userVerifier.ts +++ b/src-wallet/userVerifier.ts @@ -1,17 +1,21 @@ -import { IAddress, IVerifiable, IVerifier } from "./interface"; +import { IVerifiable } from "./interface"; import { UserPublicKey } from "./userKeys"; +interface IAddress { + pubkey(): Buffer; +} + /** * ed25519 signature verification */ -export class UserVerifier implements IVerifier { - +export class UserVerifier { publicKey: UserPublicKey; + constructor(publicKey: UserPublicKey) { this.publicKey = publicKey; } - static fromAddress(address: IAddress): IVerifier { + static fromAddress(address: IAddress): UserVerifier { let publicKey = new UserPublicKey(address.pubkey()); return new UserVerifier(publicKey); } @@ -22,7 +26,7 @@ export class UserVerifier implements IVerifier { */ verify(message: IVerifiable): boolean { return this.publicKey.verify( - message.serializeForSigning(this.publicKey.toAddress()), + message.serializeForSigning(), Buffer.from(message.getSignature().hex(), 'hex')); } } From 0f6f76a14c545a02197fd35d6680906bfbddc8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 23 Feb 2023 21:19:09 +0200 Subject: [PATCH 065/118] Simplify test object. --- src-wallet/testutils/message.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src-wallet/testutils/message.ts b/src-wallet/testutils/message.ts index 7ed55520d..e51376d2d 100644 --- a/src-wallet/testutils/message.ts +++ b/src-wallet/testutils/message.ts @@ -1,4 +1,4 @@ -import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; +import { ISignable, ISignature, IVerifiable } from "../interface"; import { Signature } from "../signature"; /** @@ -28,10 +28,6 @@ export class TestMessage implements ISignable, IVerifiable { this.signature = signature.hex(); } - applyGuardianSignature(guardianSignature: ISignature) { - this.guardianSignature = guardianSignature.hex() - } - getSignature(): ISignature { return new Signature(Buffer.from(this.signature, "hex")); } From dd6c5291839365430a1694a9f293ebc55aaf594f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 24 Feb 2023 16:34:59 +0200 Subject: [PATCH 066/118] Simplified Verify(), to receive the raw data and the signature. --- src-wallet/interface.ts | 19 ------------------- src-wallet/testutils/message.ts | 11 +++-------- src-wallet/testutils/transaction.ts | 4 ++-- src-wallet/userKeys.ts | 10 +++++----- src-wallet/userVerifier.ts | 18 +++++++++--------- src-wallet/users.spec.ts | 12 ++++++++---- 6 files changed, 27 insertions(+), 47 deletions(-) diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts index 04c4fdee3..d03e5348d 100644 --- a/src-wallet/interface.ts +++ b/src-wallet/interface.ts @@ -23,10 +23,6 @@ export interface ISigner { sign(signable: ISignable): Promise; } -export interface IVerifier { - verify(message: IVerifiable): boolean; -} - /** * An interface that defines a signable object (e.g. a transaction). */ @@ -44,18 +40,3 @@ export interface ISignable { */ applySignature(signature: ISignature, signedBy: IAddress): void; } - -/** - * Interface that defines a signed and verifiable object - */ -export interface IVerifiable { - /** - * Returns the signature that should be verified - */ - getSignature(): ISignature; - - /** - * Returns the signable object in its raw form - a sequence of bytes to be verified. - */ - serializeForSigning(signedBy?: IAddress): Buffer; -} diff --git a/src-wallet/testutils/message.ts b/src-wallet/testutils/message.ts index 1fe2f542b..3460290d7 100644 --- a/src-wallet/testutils/message.ts +++ b/src-wallet/testutils/message.ts @@ -1,10 +1,9 @@ -import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; -import { Signature } from "../signature"; +import { IAddress, ISignature } from "../interface"; /** * A dummy message used in tests. */ -export class TestMessage implements ISignable, IVerifiable { +export class TestMessage { foo: string = ""; bar: string = ""; signature: string = ""; @@ -13,7 +12,7 @@ export class TestMessage implements ISignable, IVerifiable { Object.assign(this, init); } - serializeForSigning(_signedBy: IAddress): Buffer { + serializeForSigning(): Buffer { let plainObject = { foo: this.foo, bar: this.bar @@ -26,8 +25,4 @@ export class TestMessage implements ISignable, IVerifiable { applySignature(signature: ISignature, _signedBy: IAddress): void { this.signature = signature.hex(); } - - getSignature(): ISignature { - return new Signature(Buffer.from(this.signature, "hex")); - } } diff --git a/src-wallet/testutils/transaction.ts b/src-wallet/testutils/transaction.ts index bd0d04c06..88b2ccb63 100644 --- a/src-wallet/testutils/transaction.ts +++ b/src-wallet/testutils/transaction.ts @@ -1,10 +1,10 @@ -import { IAddress, ISignable, ISignature, IVerifiable } from "../interface"; +import { IAddress, ISignature } from "../interface"; import { Signature } from "../signature"; /** * A dummy transaction used in tests. */ -export class TestTransaction implements ISignable, IVerifiable { +export class TestTransaction { nonce: number = 0; value: string = ""; receiver: string = ""; diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index eab6b92f2..aec4d211c 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -1,8 +1,8 @@ import * as tweetnacl from "tweetnacl"; -import { UserAddress } from "./userAddress"; import { guardLength } from "./assertions"; -import { parseUserKey } from "./pem"; import { IAddress } from "./interface"; +import { parseUserKey } from "./pem"; +import { UserAddress } from "./userAddress"; export const USER_SEED_LENGTH = 32; export const USER_PUBKEY_LENGTH = 32; @@ -57,13 +57,13 @@ export class UserPublicKey { constructor(buffer: Buffer) { guardLength(buffer, USER_PUBKEY_LENGTH); - + this.buffer = buffer; } - verify(message: Buffer, signature: Buffer): boolean { + verify(data: Buffer, signature: Buffer): boolean { try { - const unopenedMessage = Buffer.concat([signature, message]); + const unopenedMessage = Buffer.concat([signature, data]); const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.buffer); return unsignedMessage != null; } catch (err) { diff --git a/src-wallet/userVerifier.ts b/src-wallet/userVerifier.ts index 73e8e4f6b..eed50b21a 100644 --- a/src-wallet/userVerifier.ts +++ b/src-wallet/userVerifier.ts @@ -1,28 +1,28 @@ -import { IAddress, IVerifiable, IVerifier } from "./interface"; +import { IAddress } from "./interface"; import { UserPublicKey } from "./userKeys"; /** * ed25519 signature verification */ -export class UserVerifier implements IVerifier { +export class UserVerifier { publicKey: UserPublicKey; constructor(publicKey: UserPublicKey) { this.publicKey = publicKey; } - static fromAddress(address: IAddress): IVerifier { + static fromAddress(address: IAddress): UserVerifier { let publicKey = new UserPublicKey(address.pubkey()); return new UserVerifier(publicKey); } /** - * Verify a message's signature. - * @param message the message to be verified. + * + * @param data the raw data to be verified (e.g. an already-serialized enveloped message) + * @param signature the signature to be verified + * @returns true if the signature is valid, false otherwise */ - verify(message: IVerifiable): boolean { - return this.publicKey.verify( - message.serializeForSigning(this.publicKey.toAddress()), - Buffer.from(message.getSignature().hex(), 'hex')); + verify(data: Buffer, signature: Buffer): boolean { + return this.publicKey.verify(data, signature); } } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 98a1a2d85..ca28f2d75 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -200,7 +200,7 @@ describe("test user wallets", () => { assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); assert.equal(transaction.getSignature().hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); - assert.isTrue(verifier.verify(transaction)); + assert.isTrue(verifier.verify(Buffer.from(serialized), Buffer.from(transaction.signature, "hex"))); // Without data field transaction = new TestTransaction({ nonce: 8, @@ -235,7 +235,7 @@ describe("test user wallets", () => { assert.equal(transaction.getSignature().hex(), "c0bd2b3b33a07b9cc5ee7435228acb0936b3829c7008aacabceea35163e555e19a34def2c03a895cf36b0bcec30a7e11215c11efc0da29294a11234eb2b3b906"); }); - it("signs a general message", function () { + it("signs a general message", async function () { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); const message = new TestMessage({ @@ -243,8 +243,12 @@ describe("test user wallets", () => { bar: "world" }); - signer.sign(message); + await signer.sign(message); + + const serialized = message.serializeForSigning(); + const signature = Buffer.from(message.signature, "hex"); + assert.isNotEmpty(message.signature); - assert.isTrue(verifier.verify(message)); + assert.isTrue(verifier.verify(serialized, signature)); }); }); From b6294e74e7d57779fa9c610987e6e6b2adb9c5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 24 Feb 2023 16:56:17 +0200 Subject: [PATCH 067/118] Remove interfaces: ISigner, ISignable. Now, signer.sign() returns the signature, instead of calling applySignature() on the signed object. --- src-wallet/index.ts | 4 +-- src-wallet/interface.ts | 42 ----------------------------- src-wallet/testutils/message.ts | 7 ----- src-wallet/testutils/transaction.ts | 30 ++++++--------------- src-wallet/userKeys.ts | 3 +-- src-wallet/userSigner.ts | 33 +++++++---------------- src-wallet/userVerifier.ts | 8 ++++-- src-wallet/users.spec.ts | 41 +++++++++++++--------------- 8 files changed, 46 insertions(+), 122 deletions(-) delete mode 100644 src-wallet/interface.ts diff --git a/src-wallet/index.ts b/src-wallet/index.ts index c5d518dfd..af9ecec6a 100644 --- a/src-wallet/index.ts +++ b/src-wallet/index.ts @@ -1,8 +1,8 @@ export * from "./mnemonic"; export * from "./pem"; -export * from "./userWallet"; export * from "./userKeys"; -export * from "./validatorKeys"; export * from "./userSigner"; export * from "./userVerifier"; +export * from "./userWallet"; +export * from "./validatorKeys"; export * from "./validatorSigner"; diff --git a/src-wallet/interface.ts b/src-wallet/interface.ts deleted file mode 100644 index d03e5348d..000000000 --- a/src-wallet/interface.ts +++ /dev/null @@ -1,42 +0,0 @@ -export interface IAddress { - hex(): string; - bech32(): string; - pubkey(): Buffer; -} - -export interface ISignature { - hex(): string; -} - -/** - * An interface that defines a signing-capable object. - */ -export interface ISigner { - /** - * Gets the {@link Address} of the signer. - */ - getAddress(): IAddress; - - /** - * Signs a message (e.g. a transaction). - */ - sign(signable: ISignable): Promise; -} - -/** - * An interface that defines a signable object (e.g. a transaction). - */ -export interface ISignable { - /** - * Returns the signable object in its raw form - a sequence of bytes to be signed. - */ - serializeForSigning(signedBy: IAddress): Buffer; - - /** - * Applies the computed signature on the object itself. - * - * @param signature The computed signature - * @param signedBy The address of the {@link ISignature} - */ - applySignature(signature: ISignature, signedBy: IAddress): void; -} diff --git a/src-wallet/testutils/message.ts b/src-wallet/testutils/message.ts index 3460290d7..c115d20dc 100644 --- a/src-wallet/testutils/message.ts +++ b/src-wallet/testutils/message.ts @@ -1,12 +1,9 @@ -import { IAddress, ISignature } from "../interface"; - /** * A dummy message used in tests. */ export class TestMessage { foo: string = ""; bar: string = ""; - signature: string = ""; constructor(init?: Partial) { Object.assign(this, init); @@ -21,8 +18,4 @@ export class TestMessage { let serialized = JSON.stringify(plainObject); return Buffer.from(serialized); } - - applySignature(signature: ISignature, _signedBy: IAddress): void { - this.signature = signature.hex(); - } } diff --git a/src-wallet/testutils/transaction.ts b/src-wallet/testutils/transaction.ts index 88b2ccb63..f59b13b0a 100644 --- a/src-wallet/testutils/transaction.ts +++ b/src-wallet/testutils/transaction.ts @@ -1,6 +1,3 @@ -import { IAddress, ISignature } from "../interface"; -import { Signature } from "../signature"; - /** * A dummy transaction used in tests. */ @@ -16,40 +13,29 @@ export class TestTransaction { options: number = 0; sender: string = ""; - signature: string = ""; constructor(init?: Partial) { Object.assign(this, init); } - serializeForSigning(signedBy: IAddress): Buffer { - let sender = signedBy.bech32(); - let dataEncoded = this.data ? Buffer.from(this.data).toString("base64") : undefined; - let options = this.options ? this.options : undefined; + serializeForSigning(): Buffer { + const dataEncoded = this.data ? Buffer.from(this.data).toString("base64") : undefined; + const options = this.options ? this.options : undefined; - let plainObject = { + const plainObject = { nonce: this.nonce, value: this.value, receiver: this.receiver, - sender: sender, + sender: this.sender, gasPrice: this.gasPrice, gasLimit: this.gasLimit, data: dataEncoded, chainID: this.chainID, - version: this.version, - options: options + options: options, + version: this.version }; - let serialized = JSON.stringify(plainObject); + const serialized = JSON.stringify(plainObject); return Buffer.from(serialized); } - - applySignature(signature: ISignature, signedBy: IAddress): void { - this.sender = signedBy.bech32(); - this.signature = signature.hex(); - } - - getSignature(): ISignature { - return new Signature(Buffer.from(this.signature, "hex")); - } } diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index aec4d211c..42912cf4b 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -1,6 +1,5 @@ import * as tweetnacl from "tweetnacl"; import { guardLength } from "./assertions"; -import { IAddress } from "./interface"; import { parseUserKey } from "./pem"; import { UserAddress } from "./userAddress"; @@ -76,7 +75,7 @@ export class UserPublicKey { return this.buffer.toString("hex"); } - toAddress(): IAddress { + toAddress(): UserAddress { return new UserAddress(this.buffer); } diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index c7cdde4e0..d5ab20733 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -1,20 +1,19 @@ -import { IAddress, ISignable, ISigner } from "./interface"; -import { Signature } from "./signature"; +import { ErrSignerCannotSign } from "./errors"; +import { UserAddress } from "./userAddress"; import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; -import { ErrSignerCannotSign } from "./errors"; /** * ed25519 signer */ -export class UserSigner implements ISigner { - private readonly secretKey: UserSecretKey; +export class UserSigner { + protected readonly secretKey: UserSecretKey; constructor(secretKey: UserSecretKey) { this.secretKey = secretKey; } - static fromWallet(keyFileObject: any, password: string): ISigner { + static fromWallet(keyFileObject: any, password: string): UserSigner { let secretKey = UserWallet.decryptSecretKey(keyFileObject, password); return new UserSigner(secretKey); } @@ -23,32 +22,20 @@ export class UserSigner implements ISigner { let secretKey = UserSecretKey.fromPem(text, index); return new UserSigner(secretKey); } - - /** - * Signs a message. - * @param signable the message to be signed (e.g. a {@link Transaction}). - */ - async sign(signable: ISignable): Promise { + + async sign(data: Buffer): Promise { try { - this.trySign(signable); + const signature = this.secretKey.sign(data); + return signature; } catch (err: any) { throw new ErrSignerCannotSign(err); } } - private trySign(signable: ISignable) { - let signedBy = this.getAddress(); - let bufferToSign = signable.serializeForSigning(signedBy); - let signatureBuffer = this.secretKey.sign(bufferToSign); - let signature = new Signature(signatureBuffer); - - signable.applySignature(signature, signedBy); - } - /** * Gets the address of the signer. */ - getAddress(): IAddress { + getAddress(): UserAddress { return this.secretKey.generatePublicKey().toAddress(); } } diff --git a/src-wallet/userVerifier.ts b/src-wallet/userVerifier.ts index eed50b21a..78302943f 100644 --- a/src-wallet/userVerifier.ts +++ b/src-wallet/userVerifier.ts @@ -1,12 +1,16 @@ -import { IAddress } from "./interface"; import { UserPublicKey } from "./userKeys"; +interface IAddress { + pubkey(): Buffer; +} + + /** * ed25519 signature verification */ export class UserVerifier { - publicKey: UserPublicKey; + constructor(publicKey: UserPublicKey) { this.publicKey = publicKey; } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index ca28f2d75..712e94edb 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -5,7 +5,6 @@ import { Mnemonic } from "./mnemonic"; import { TestMessage } from "./testutils/message"; import { TestTransaction } from "./testutils/transaction"; import { DummyMnemonic, DummyMnemonicOf12Words, DummyPassword, loadTestKeystore, loadTestWallet, TestWallet } from "./testutils/wallets"; -import { UserAddress } from "./userAddress"; import { UserSecretKey } from "./userKeys"; import { UserSigner } from "./userSigner"; import { UserVerifier } from "./userVerifier"; @@ -182,7 +181,6 @@ describe("test user wallets", () => { it("should sign transactions", async () => { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); - let sender = UserAddress.fromBech32("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"); // With data field let transaction = new TestTransaction({ @@ -192,15 +190,15 @@ describe("test user wallets", () => { gasPrice: 1000000000, gasLimit: 50000, data: "foo", - chainID: "1" + chainID: "1", }); - let serialized = transaction.serializeForSigning(sender).toString(); - await signer.sign(transaction); + let serialized = transaction.serializeForSigning(); + let signature = await signer.sign(serialized); - assert.equal(serialized, `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); - assert.equal(transaction.getSignature().hex(), "b5fddb8c16fa7f6123cb32edc854f1e760a3eb62c6dc420b5a4c0473c58befd45b621b31a448c5b59e21428f2bc128c80d0ee1caa4f2bf05a12be857ad451b00"); - assert.isTrue(verifier.verify(Buffer.from(serialized), Buffer.from(transaction.signature, "hex"))); + assert.equal(serialized.toString(), `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); + assert.equal(signature.toString("hex"), "a3b61a2fe461f3393c42e6cb0477a6b52ffd92168f10c111f6aa8d0a310ee0c314fae0670f8313f1ad992933ac637c61a8ff20cc20b6a8b2260a4af1a120a70d"); + assert.isTrue(verifier.verify(serialized, signature)); // Without data field transaction = new TestTransaction({ nonce: 8, @@ -211,17 +209,17 @@ describe("test user wallets", () => { chainID: "1" }); - serialized = transaction.serializeForSigning(sender).toString(); - await signer.sign(transaction); + serialized = transaction.serializeForSigning(); + signature = await signer.sign(serialized); - assert.equal(serialized, `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); - assert.equal(transaction.getSignature().hex(), "4a6d8186eae110894e7417af82c9bf9592696c0600faf110972e0e5310d8485efc656b867a2336acec2b4c1e5f76c9cc70ba1803c6a46455ed7f1e2989a90105"); + assert.equal(serialized.toString(), `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); + assert.equal(signature.toString("hex"), "f136c901d37349a7da8cfe3ab5ec8ef333b0bc351517c0e9bef9eb9704aed3077bf222769cade5ff29dffe5f42e4f0c5e0b068bdba90cd2cb41da51fd45d5a03"); }); it("should sign transactions using PEM files", async () => { - let signer = UserSigner.fromPem(alice.pemFileText); + const signer = UserSigner.fromPem(alice.pemFileText); - let transaction = new TestTransaction({ + const transaction = new TestTransaction({ nonce: 0, value: "0", receiver: "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", @@ -231,24 +229,23 @@ describe("test user wallets", () => { chainID: "1" }); - await signer.sign(transaction); - assert.equal(transaction.getSignature().hex(), "c0bd2b3b33a07b9cc5ee7435228acb0936b3829c7008aacabceea35163e555e19a34def2c03a895cf36b0bcec30a7e11215c11efc0da29294a11234eb2b3b906"); + const signature = await signer.sign(transaction.serializeForSigning()); + assert.equal(signature.toString("hex"), "ba4fa95fea1402e4876abf1d5a510615aab374ee48bb76f5230798a7d3f2fcae6ba91ba56c6d62e6e7003ce531ff02f219cb7218dd00dd2ca650ba747f19640a"); }); it("signs a general message", async function () { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); + const message = new TestMessage({ foo: "hello", bar: "world" }); - await signer.sign(message); - - const serialized = message.serializeForSigning(); - const signature = Buffer.from(message.signature, "hex"); + const data = message.serializeForSigning(); + const signature = await signer.sign(data); - assert.isNotEmpty(message.signature); - assert.isTrue(verifier.verify(serialized, signature)); + assert.isTrue(verifier.verify(data, signature)); + assert.isFalse(verifier.verify(Buffer.from("hello"), signature)); }); }); From ec6b3a304188ed22359457a1b81dc35c498422ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Fri, 24 Feb 2023 16:58:39 +0200 Subject: [PATCH 068/118] Fix after self-review. --- src-wallet/testutils/transaction.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-wallet/testutils/transaction.ts b/src-wallet/testutils/transaction.ts index f59b13b0a..a60a7c04d 100644 --- a/src-wallet/testutils/transaction.ts +++ b/src-wallet/testutils/transaction.ts @@ -31,8 +31,8 @@ export class TestTransaction { gasLimit: this.gasLimit, data: dataEncoded, chainID: this.chainID, - options: options, - version: this.version + version: this.version, + options: options }; const serialized = JSON.stringify(plainObject); From b15f6d18805f16991ad4bb0d8b0ca066e7790096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 13 Mar 2023 15:59:03 +0200 Subject: [PATCH 069/118] Bit of decoupling. --- src-wallet/userSigner.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index d5ab20733..144080f9a 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -3,13 +3,23 @@ import { UserAddress } from "./userAddress"; import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; +interface IUserSecretKey { + sign(message: Buffer): Buffer; + generatePublicKey(): IUserPublicKey; +} + +interface IUserPublicKey { + toAddress(): { bech32(): string; }; +} + + /** * ed25519 signer */ export class UserSigner { - protected readonly secretKey: UserSecretKey; + protected readonly secretKey: IUserSecretKey; - constructor(secretKey: UserSecretKey) { + constructor(secretKey: IUserSecretKey) { this.secretKey = secretKey; } @@ -36,6 +46,7 @@ export class UserSigner { * Gets the address of the signer. */ getAddress(): UserAddress { - return this.secretKey.generatePublicKey().toAddress(); + const bech32 = this.secretKey.generatePublicKey().toAddress().bech32(); + return UserAddress.fromBech32(bech32); } } From 19dc566bfd5b85b5aab59ddbe5b8222234c86466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 21 Mar 2023 13:55:11 +0200 Subject: [PATCH 070/118] @noble/ed25519 for sign & verify (user wallets). --- src-wallet/crypto/pubkeyEncryptor.ts | 2 +- src-wallet/crypto/randomness.ts | 8 ++--- src-wallet/userKeys.ts | 27 +++++++------- src-wallet/usersBenchmark.spec.ts | 53 ++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 src-wallet/usersBenchmark.spec.ts diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src-wallet/crypto/pubkeyEncryptor.ts index b3f6517c1..0c89bf16c 100644 --- a/src-wallet/crypto/pubkeyEncryptor.ts +++ b/src-wallet/crypto/pubkeyEncryptor.ts @@ -7,7 +7,7 @@ import { X25519EncryptedData } from "./x25519EncryptedData"; export class PubkeyEncryptor { static encrypt(data: Buffer, recipientPubKey: UserPublicKey, authSecretKey: UserSecretKey): X25519EncryptedData { - // create a new x225519 keypair that will be used for EDH + // 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) { diff --git a/src-wallet/crypto/randomness.ts b/src-wallet/crypto/randomness.ts index f045dc676..c6355ff87 100644 --- a/src-wallet/crypto/randomness.ts +++ b/src-wallet/crypto/randomness.ts @@ -1,5 +1,5 @@ -import nacl from "tweetnacl"; -import {v4 as uuidv4} from "uuid"; +import { utils } from "@noble/ed25519"; +import { v4 as uuidv4 } from "uuid"; const crypto = require("crypto"); export class Randomness { @@ -8,8 +8,8 @@ export class Randomness { id: string; constructor(init?: Partial) { - this.salt = init?.salt || Buffer.from(nacl.randomBytes(32)); - this.iv = init?.iv || Buffer.from(nacl.randomBytes(16)); + 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) }); } } diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 42912cf4b..d8584d023 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -1,4 +1,5 @@ -import * as tweetnacl from "tweetnacl"; +import * as ed from "@noble/ed25519"; +import { sha512 } from "@noble/hashes/sha512"; import { guardLength } from "./assertions"; import { parseUserKey } from "./pem"; import { UserAddress } from "./userAddress"; @@ -6,6 +7,9 @@ import { UserAddress } from "./userAddress"; export const USER_SEED_LENGTH = 32; export const USER_PUBKEY_LENGTH = 32; +// See: https://github.com/paulmillr/noble-ed25519 +ed.utils.sha512Sync = (...m) => sha512(ed.utils.concatBytes(...m)); + export class UserSecretKey { private readonly buffer: Buffer; @@ -27,18 +31,12 @@ export class UserSecretKey { } generatePublicKey(): UserPublicKey { - let keyPair = tweetnacl.sign.keyPair.fromSeed(new Uint8Array(this.buffer)); - let buffer = Buffer.from(keyPair.publicKey); + const buffer = ed.sync.getPublicKey(this.buffer); return new UserPublicKey(buffer); } sign(message: Buffer): Buffer { - let pair = tweetnacl.sign.keyPair.fromSeed(new Uint8Array(this.buffer)); - let signingKey = pair.secretKey; - let signature = tweetnacl.sign(new Uint8Array(message), signingKey); - // "tweetnacl.sign()" returns the concatenated [signature, message], therfore we remove the appended message: - signature = signature.slice(0, signature.length - message.length); - + const signature = ed.sync.sign(message, this.buffer); return Buffer.from(signature); } @@ -54,18 +52,17 @@ export class UserSecretKey { export class UserPublicKey { private readonly buffer: Buffer; - constructor(buffer: Buffer) { + constructor(buffer: Uint8Array) { guardLength(buffer, USER_PUBKEY_LENGTH); - this.buffer = buffer; + this.buffer = Buffer.from(buffer); } verify(data: Buffer, signature: Buffer): boolean { try { - const unopenedMessage = Buffer.concat([signature, data]); - const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.buffer); - return unsignedMessage != null; - } catch (err) { + const ok = ed.sync.verify(signature, data, this.buffer); + return ok; + } catch (err: any) { console.error(err); return false; } diff --git a/src-wallet/usersBenchmark.spec.ts b/src-wallet/usersBenchmark.spec.ts new file mode 100644 index 000000000..6f86d0aac --- /dev/null +++ b/src-wallet/usersBenchmark.spec.ts @@ -0,0 +1,53 @@ +import { utils } from "@noble/ed25519"; +import { assert } from "chai"; +import { UserPublicKey, UserSecretKey } from "./userKeys"; + +describe("behchmark sign and verify", () => { + + it.only("should sign and verify", async function () { + this.timeout(60000); + + const n = 10000; + const secretKeys: UserSecretKey[] = []; + const publicKeys: UserPublicKey[] = []; + const messages: Buffer[] = []; + const goodSignatures: Buffer[] = []; + + for (let i = 0; i < n; i++) { + const secretKey = new UserSecretKey(Buffer.from(utils.randomBytes(32))); + const publicKey = secretKey.generatePublicKey(); + const message = Buffer.from(utils.randomBytes(256)); + + secretKeys.push(secretKey); + publicKeys.push(publicKey); + messages.push(message); + } + + console.time("sign"); + + for (let i = 0; i < n; i++) { + const signature = secretKeys[i].sign(messages[i]); + goodSignatures.push(signature); + } + + console.timeEnd("sign"); + + console.time("verify (good)"); + + for (let i = 0; i < n; i++) { + const ok = publicKeys[i].verify(messages[i], goodSignatures[i]); + assert.isTrue(ok); + } + + console.timeEnd("verify (good)"); + + console.time("verify (bad)"); + + for (let i = 0; i < n; i++) { + const ok = publicKeys[i].verify(messages[messages.length - i - 1], goodSignatures[i]); + assert.isFalse(ok); + } + + console.timeEnd("verify (bad)"); + }); +}); From 88bc18d4909ed6e440156ea8f0a9c730cf70ffe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 21 Mar 2023 15:14:09 +0200 Subject: [PATCH 071/118] Add benchmark for sign & verify (before switching to noble crypto libs). --- src-wallet/usersBenchmark.spec.ts | 53 +++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src-wallet/usersBenchmark.spec.ts diff --git a/src-wallet/usersBenchmark.spec.ts b/src-wallet/usersBenchmark.spec.ts new file mode 100644 index 000000000..27b87a284 --- /dev/null +++ b/src-wallet/usersBenchmark.spec.ts @@ -0,0 +1,53 @@ +import { assert } from "chai"; +import nacl from "tweetnacl"; +import { UserPublicKey, UserSecretKey } from "./userKeys"; + +describe("behchmark sign and verify", () => { + it("should sign and verify", async function () { + this.timeout(60000); + + const n = 1000; + const secretKeys: UserSecretKey[] = []; + const publicKeys: UserPublicKey[] = []; + const messages: Buffer[] = []; + const goodSignatures: Buffer[] = []; + + for (let i = 0; i < n; i++) { + const secretKey = new UserSecretKey(Buffer.from(nacl.randomBytes(32))); + const publicKey = secretKey.generatePublicKey(); + const message = Buffer.from(nacl.randomBytes(256)); + + secretKeys.push(secretKey); + publicKeys.push(publicKey); + messages.push(message); + } + + console.info(`N = ${n}`); + console.time("sign"); + + for (let i = 0; i < n; i++) { + const signature = secretKeys[i].sign(messages[i]); + goodSignatures.push(signature); + } + + console.timeEnd("sign"); + + console.time("verify (good)"); + + for (let i = 0; i < n; i++) { + const ok = publicKeys[i].verify(messages[i], goodSignatures[i]); + assert.isTrue(ok); + } + + console.timeEnd("verify (good)"); + + console.time("verify (bad)"); + + for (let i = 0; i < n; i++) { + const ok = publicKeys[i].verify(messages[messages.length - i - 1], goodSignatures[i]); + assert.isFalse(ok); + } + + console.timeEnd("verify (bad)"); + }); +}); From 5dc401e991b3396a9a9fe61c84a2b45e027a0d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 21 Mar 2023 17:38:15 +0200 Subject: [PATCH 072/118] Fix after self-review. --- src-wallet/userKeys.ts | 1 + src-wallet/usersBenchmark.spec.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index d8584d023..89a163b32 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -8,6 +8,7 @@ export const USER_SEED_LENGTH = 32; export const USER_PUBKEY_LENGTH = 32; // See: https://github.com/paulmillr/noble-ed25519 +// In a future version of sdk-wallet, we'll switch to using the async functions of noble-ed25519. ed.utils.sha512Sync = (...m) => sha512(ed.utils.concatBytes(...m)); export class UserSecretKey { diff --git a/src-wallet/usersBenchmark.spec.ts b/src-wallet/usersBenchmark.spec.ts index 1d1fc065e..eeb318e0a 100644 --- a/src-wallet/usersBenchmark.spec.ts +++ b/src-wallet/usersBenchmark.spec.ts @@ -22,6 +22,8 @@ describe("behchmark sign and verify", () => { messages.push(message); } + console.info(`N = ${n}`); + console.time("sign"); for (let i = 0; i < n; i++) { From 8c341c23a81621045a1dc0c2af95d0d972c7e4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 8 May 2023 13:16:03 +0300 Subject: [PATCH 073/118] Automatically handle both kinds of keystores in "UserWallet.loadSecretKey(), "UserSigner.fromWallet()". --- src-wallet/userSigner.ts | 4 ++-- src-wallet/userWallet.ts | 19 +++++++++++++++++++ src-wallet/users.spec.ts | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 144080f9a..a1051d4e8 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -23,8 +23,8 @@ export class UserSigner { this.secretKey = secretKey; } - static fromWallet(keyFileObject: any, password: string): UserSigner { - let secretKey = UserWallet.decryptSecretKey(keyFileObject, password); + static fromWallet(keyFileObject: any, password: string, addressIndex?: number): UserSigner { + const secretKey = UserWallet.loadSecretKey(keyFileObject, password, addressIndex); return new UserSigner(secretKey); } diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 8c6e36681..2bee3209e 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -65,6 +65,25 @@ export class UserWallet { }); } + static loadSecretKey(keyFileObject: any, password: string, addressIndex?: number): UserSecretKey { + const kind = keyFileObject.kind || UserWalletKind.SecretKey; + + if (kind == UserWalletKind.SecretKey) { + if (addressIndex !== undefined) { + throw new Err("addressIndex must not be provided when kind == 'secretKey'"); + } + + return UserWallet.decryptSecretKey(keyFileObject, password); + } + + if (kind == UserWalletKind.Mnemonic) { + const mnemonic = this.decryptMnemonic(keyFileObject, password); + return mnemonic.deriveKey(addressIndex || 0); + } + + throw new Err(`Unknown kind: ${kind}`); + } + /** * Copied from: https://github.com/multiversx/mx-deprecated-core-js/blob/v1.28.0/src/account.js#L42 * Notes: adjustements (code refactoring, no change in logic), in terms of: diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index b2f47b8c0..6e92c6bb7 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -178,6 +178,27 @@ describe("test user wallets", () => { assert.deepEqual(dummyWallet.toJSON(), expectedDummyWallet); }); + it("should loadSecretKey, but without 'kind' field", async function () { + const keyFileObject = await loadTestKeystore("withoutKind.json"); + const secretKey = UserWallet.loadSecretKey(keyFileObject, password); + + assert.equal(secretKey.generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + }); + + it("should throw when calling loadSecretKey with unecessary address index", async function () { + const keyFileObject = await loadTestKeystore("alice.json"); + + assert.throws(() => UserWallet.loadSecretKey(keyFileObject, password, 42), "addressIndex must not be provided when kind == 'secretKey'"); + }); + + it("should loadSecretKey with mnemonic", async function () { + const keyFileObject = await loadTestKeystore("withDummyMnemonic.json"); + + assert.equal(UserWallet.loadSecretKey(keyFileObject, password, 0).generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(UserWallet.loadSecretKey(keyFileObject, password, 1).generatePublicKey().toAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + assert.equal(UserWallet.loadSecretKey(keyFileObject, password, 2).generatePublicKey().toAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + }); + it("should sign transactions", async () => { let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); From 67b19c80722fc0ea1e42c9249a655af6f5a593de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Mon, 8 May 2023 13:23:11 +0300 Subject: [PATCH 074/118] Add extra unit test. --- src-wallet/users.spec.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 6e92c6bb7..da4136edb 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -323,4 +323,15 @@ describe("test user wallets", () => { assert.isTrue(verifier.verify(data, signature)); assert.isFalse(verifier.verify(Buffer.from("hello"), signature)); }); + + it("should create UserSigner from wallet", async function () { + const keyFileObjectWithoutKind = await loadTestKeystore("withoutKind.json"); + const keyFileObjectWithMnemonic = await loadTestKeystore("withDummyMnemonic.json"); + + assert.equal(UserSigner.fromWallet(keyFileObjectWithoutKind, password).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + }); }); From 19d14261f24b82eff760ab70f9cfa6b27d79bab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 9 May 2023 13:54:19 +0300 Subject: [PATCH 075/118] Fix after review. --- src-wallet/userSigner.ts | 2 +- src-wallet/userWallet.ts | 2 +- src-wallet/users.spec.ts | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index a1051d4e8..7dfb52811 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -24,7 +24,7 @@ export class UserSigner { } static fromWallet(keyFileObject: any, password: string, addressIndex?: number): UserSigner { - const secretKey = UserWallet.loadSecretKey(keyFileObject, password, addressIndex); + const secretKey = UserWallet.decrypt(keyFileObject, password, addressIndex); return new UserSigner(secretKey); } diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 2bee3209e..3d275993e 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -65,7 +65,7 @@ export class UserWallet { }); } - static loadSecretKey(keyFileObject: any, password: string, addressIndex?: number): UserSecretKey { + static decrypt(keyFileObject: any, password: string, addressIndex?: number): UserSecretKey { const kind = keyFileObject.kind || UserWalletKind.SecretKey; if (kind == UserWalletKind.SecretKey) { diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index da4136edb..469cbd01b 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -180,7 +180,7 @@ describe("test user wallets", () => { it("should loadSecretKey, but without 'kind' field", async function () { const keyFileObject = await loadTestKeystore("withoutKind.json"); - const secretKey = UserWallet.loadSecretKey(keyFileObject, password); + const secretKey = UserWallet.decrypt(keyFileObject, password); assert.equal(secretKey.generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); }); @@ -188,15 +188,15 @@ describe("test user wallets", () => { it("should throw when calling loadSecretKey with unecessary address index", async function () { const keyFileObject = await loadTestKeystore("alice.json"); - assert.throws(() => UserWallet.loadSecretKey(keyFileObject, password, 42), "addressIndex must not be provided when kind == 'secretKey'"); + assert.throws(() => UserWallet.decrypt(keyFileObject, password, 42), "addressIndex must not be provided when kind == 'secretKey'"); }); it("should loadSecretKey with mnemonic", async function () { const keyFileObject = await loadTestKeystore("withDummyMnemonic.json"); - assert.equal(UserWallet.loadSecretKey(keyFileObject, password, 0).generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.equal(UserWallet.loadSecretKey(keyFileObject, password, 1).generatePublicKey().toAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - assert.equal(UserWallet.loadSecretKey(keyFileObject, password, 2).generatePublicKey().toAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + assert.equal(UserWallet.decrypt(keyFileObject, password, 0).generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(UserWallet.decrypt(keyFileObject, password, 1).generatePublicKey().toAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); + assert.equal(UserWallet.decrypt(keyFileObject, password, 2).generatePublicKey().toAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); }); it("should sign transactions", async () => { From 322257a352287ff7860868972de2318461fd719c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 24 May 2023 13:08:40 +0300 Subject: [PATCH 076/118] Workaround: receive Uint8Array instead of Buffer. --- src-wallet/userKeys.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 89a163b32..9c38410a1 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -12,9 +12,9 @@ export const USER_PUBKEY_LENGTH = 32; ed.utils.sha512Sync = (...m) => sha512(ed.utils.concatBytes(...m)); export class UserSecretKey { - private readonly buffer: Buffer; + private readonly buffer: Uint8Array; - constructor(buffer: Buffer) { + constructor(buffer: Uint8Array) { guardLength(buffer, USER_SEED_LENGTH); this.buffer = buffer; @@ -23,7 +23,7 @@ export class UserSecretKey { static fromString(value: string): UserSecretKey { guardLength(value, USER_SEED_LENGTH * 2); - let buffer = Buffer.from(value, "hex"); + const buffer = Buffer.from(value, "hex"); return new UserSecretKey(buffer); } @@ -42,11 +42,11 @@ export class UserSecretKey { } hex(): string { - return this.buffer.toString("hex"); + return Buffer.from(this.buffer).toString("hex"); } valueOf(): Buffer { - return this.buffer; + return Buffer.from(this.buffer); } } From 36d64fd530ade64e76d5c38d0bcbe54f1863715b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 24 May 2023 13:15:15 +0300 Subject: [PATCH 077/118] Reference buffer in globals (temporary workaround). --- src-wallet/globals.ts | 3 +++ src-wallet/index.ts | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 src-wallet/globals.ts diff --git a/src-wallet/globals.ts b/src-wallet/globals.ts new file mode 100644 index 000000000..6169db45b --- /dev/null +++ b/src-wallet/globals.ts @@ -0,0 +1,3 @@ +if (!global.Buffer) { + global.Buffer = require("buffer").Buffer; +} diff --git a/src-wallet/index.ts b/src-wallet/index.ts index af9ecec6a..7427d20a7 100644 --- a/src-wallet/index.ts +++ b/src-wallet/index.ts @@ -1,3 +1,5 @@ +require("./globals"); + export * from "./mnemonic"; export * from "./pem"; export * from "./userKeys"; From c91a71db4beed1f09496f42e0c0a64d558d3b51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 24 May 2023 13:24:56 +0300 Subject: [PATCH 078/118] Workaround on constructor. --- src-wallet/userKeys.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 9c38410a1..71dfba0d0 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -12,12 +12,12 @@ export const USER_PUBKEY_LENGTH = 32; ed.utils.sha512Sync = (...m) => sha512(ed.utils.concatBytes(...m)); export class UserSecretKey { - private readonly buffer: Uint8Array; + private readonly buffer: Buffer; constructor(buffer: Uint8Array) { guardLength(buffer, USER_SEED_LENGTH); - this.buffer = buffer; + this.buffer = Buffer.from(buffer); } static fromString(value: string): UserSecretKey { @@ -42,11 +42,11 @@ export class UserSecretKey { } hex(): string { - return Buffer.from(this.buffer).toString("hex"); + return this.buffer.toString("hex"); } valueOf(): Buffer { - return Buffer.from(this.buffer); + return this.buffer; } } From ad1bbe9f76d1afeba101a1813cc1700d0ae7cee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 24 May 2023 13:33:40 +0300 Subject: [PATCH 079/118] Fix get public key. --- src-wallet/userKeys.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 71dfba0d0..82c63325d 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -32,7 +32,7 @@ export class UserSecretKey { } generatePublicKey(): UserPublicKey { - const buffer = ed.sync.getPublicKey(this.buffer); + const buffer = ed.sync.getPublicKey(new Uint8Array(this.buffer)); return new UserPublicKey(buffer); } From 351abcb6d109aac6a316c793b41a627485781cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 24 May 2023 13:43:31 +0300 Subject: [PATCH 080/118] Add proper casts. --- src-wallet/globals.ts | 3 --- src-wallet/index.ts | 2 -- src-wallet/userKeys.ts | 4 ++-- 3 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 src-wallet/globals.ts diff --git a/src-wallet/globals.ts b/src-wallet/globals.ts deleted file mode 100644 index 6169db45b..000000000 --- a/src-wallet/globals.ts +++ /dev/null @@ -1,3 +0,0 @@ -if (!global.Buffer) { - global.Buffer = require("buffer").Buffer; -} diff --git a/src-wallet/index.ts b/src-wallet/index.ts index 7427d20a7..af9ecec6a 100644 --- a/src-wallet/index.ts +++ b/src-wallet/index.ts @@ -1,5 +1,3 @@ -require("./globals"); - export * from "./mnemonic"; export * from "./pem"; export * from "./userKeys"; diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 82c63325d..ba1177772 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -37,7 +37,7 @@ export class UserSecretKey { } sign(message: Buffer): Buffer { - const signature = ed.sync.sign(message, this.buffer); + const signature = ed.sync.sign(new Uint8Array(message), new Uint8Array(this.buffer)); return Buffer.from(signature); } @@ -61,7 +61,7 @@ export class UserPublicKey { verify(data: Buffer, signature: Buffer): boolean { try { - const ok = ed.sync.verify(signature, data, this.buffer); + const ok = ed.sync.verify(new Uint8Array(signature), new Uint8Array(data), new Uint8Array(this.buffer)); return ok; } catch (err: any) { console.error(err); From f46cc4cef814a9657df4985301191bfec6a629a7 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 6 Dec 2023 11:07:27 +0200 Subject: [PATCH 081/118] add extra check --- src-wallet/userWallet.ts | 6 +++++- src-wallet/users.spec.ts | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 3d275993e..fea541f07 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -95,7 +95,11 @@ export class UserWallet { * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { - // Here, we do not check the "kind" field. Older keystore files (holding only secret keys) do not have this field. + // Here, we check the "kind" field only for files that have it. Older keystore files (holding only secret keys) do not have this field. + const kind = keyFileObject.kind; + if (kind && kind !== UserWalletKind.SecretKey){ + throw new Err(`Expected kind to be ${UserWalletKind.SecretKey}, but it was ${kind}.`); + } const encryptedData = UserWallet.edFromJSON(keyFileObject); diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 469cbd01b..7337da0d9 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -334,4 +334,16 @@ describe("test user wallets", () => { assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); }); + + it.only("should throw error when decrypting secret key with keystore-mnemonic file", async function () { + const userWallet = UserWallet.fromMnemonic({ + mnemonic: DummyMnemonic, + password: `` + }); + const keystoreMnemonic = userWallet.toJSON(); + + assert.throws(() => { + UserWallet.decryptSecretKey(keystoreMnemonic, ``) + }, `Expected kind to be secretKey, but it was mnemonic.`); + }); }); From 87eb57dda6d250369e75fe0588c9ab22f8026650 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 6 Dec 2023 11:08:39 +0200 Subject: [PATCH 082/118] remove only --- src-wallet/users.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 7337da0d9..8b547d44c 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -335,7 +335,7 @@ describe("test user wallets", () => { assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); }); - it.only("should throw error when decrypting secret key with keystore-mnemonic file", async function () { + it("should throw error when decrypting secret key with keystore-mnemonic file", async function () { const userWallet = UserWallet.fromMnemonic({ mnemonic: DummyMnemonic, password: `` From cc4d2e643430f446cbf120d9452fd3e7bc26a472 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 6 Dec 2023 15:24:46 +0200 Subject: [PATCH 083/118] add extra assert for unit test --- src-wallet/testdata/withDummySecretKey.json | 23 +++++++++++++++++++++ src-wallet/users.spec.ts | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 src-wallet/testdata/withDummySecretKey.json diff --git a/src-wallet/testdata/withDummySecretKey.json b/src-wallet/testdata/withDummySecretKey.json new file mode 100644 index 000000000..f7921ca5a --- /dev/null +++ b/src-wallet/testdata/withDummySecretKey.json @@ -0,0 +1,23 @@ +{ + "version": 4, + "kind": "secretKey", + "id": "c1d4b111-b8d2-4916-a213-bcfd237edd29", + "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", + "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + "crypto": { + "ciphertext": "75fbe213fc1964ce03100cf7d873748edf83a02631c8af9abdb23d210b9a2a15940bea2e56718f7bd710a938df5eb424c629e6a39b6ee056ed80d6e5f3b97791", + "cipherparams": { + "iv": "226d13be12373603af2b4edefcaa436f" + }, + "cipher": "aes-128-ctr", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "salt": "d57862c212bac142a89da97fb9bf9f5c91c8e8ddba952262dafe928e1c8a9906", + "n": 4096, + "r": 8, + "p": 1 + }, + "mac": "5bab92263237c5d595f565622dd2e61ea3dfd43580cecda7fd2f42d469b42e7f" + } +} \ No newline at end of file diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 8b547d44c..2c6c508b2 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -327,9 +327,11 @@ describe("test user wallets", () => { it("should create UserSigner from wallet", async function () { const keyFileObjectWithoutKind = await loadTestKeystore("withoutKind.json"); const keyFileObjectWithMnemonic = await loadTestKeystore("withDummyMnemonic.json"); + const keyFileObjectWithSecretKey = await loadTestKeystore("withDummySecretKey.json"); assert.equal(UserSigner.fromWallet(keyFileObjectWithoutKind, password).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal(UserSigner.fromWallet(keyFileObjectWithSecretKey, password).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); From 9e797c5ea3fb7c17af63a3da0cd70d816f3bacb8 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Wed, 6 Dec 2023 16:33:39 +0200 Subject: [PATCH 084/118] add new line --- src-wallet/testdata/withDummySecretKey.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/testdata/withDummySecretKey.json b/src-wallet/testdata/withDummySecretKey.json index f7921ca5a..cfba75528 100644 --- a/src-wallet/testdata/withDummySecretKey.json +++ b/src-wallet/testdata/withDummySecretKey.json @@ -20,4 +20,4 @@ }, "mac": "5bab92263237c5d595f565622dd2e61ea3dfd43580cecda7fd2f42d469b42e7f" } -} \ No newline at end of file +} From 1366cf135658c2e8a27d231a605e7133d7eacf13 Mon Sep 17 00:00:00 2001 From: Alexandru Popenta Date: Thu, 7 Dec 2023 11:25:50 +0200 Subject: [PATCH 085/118] make error more explicit --- src-wallet/userWallet.ts | 4 ++-- src-wallet/users.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index fea541f07..2a0f04480 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -98,7 +98,7 @@ export class UserWallet { // Here, we check the "kind" field only for files that have it. Older keystore files (holding only secret keys) do not have this field. const kind = keyFileObject.kind; if (kind && kind !== UserWalletKind.SecretKey){ - throw new Err(`Expected kind to be ${UserWalletKind.SecretKey}, but it was ${kind}.`); + throw new Err(`Expected keystore kind to be ${UserWalletKind.SecretKey}, but it was ${kind}.`); } const encryptedData = UserWallet.edFromJSON(keyFileObject); @@ -115,7 +115,7 @@ export class UserWallet { static decryptMnemonic(keyFileObject: any, password: string): Mnemonic { if (keyFileObject.kind != UserWalletKind.Mnemonic) { - throw new Err(`Expected kind to be ${UserWalletKind.Mnemonic}, but it was ${keyFileObject.kind}.`); + throw new Err(`Expected keystore kind to be ${UserWalletKind.Mnemonic}, but it was ${keyFileObject.kind}.`); } const encryptedData = UserWallet.edFromJSON(keyFileObject); diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 2c6c508b2..43cd1b464 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -346,6 +346,6 @@ describe("test user wallets", () => { assert.throws(() => { UserWallet.decryptSecretKey(keystoreMnemonic, ``) - }, `Expected kind to be secretKey, but it was mnemonic.`); + }, `Expected keystore kind to be secretKey, but it was mnemonic.`); }); }); From bc60489599529a4e0e33a023f4cf52484c5bc52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 17 Jan 2024 17:21:19 +0200 Subject: [PATCH 086/118] Remove Axios as dev-dependency. --- src-wallet/testutils/files.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src-wallet/testutils/files.ts b/src-wallet/testutils/files.ts index 09abdf1fe..51863430e 100644 --- a/src-wallet/testutils/files.ts +++ b/src-wallet/testutils/files.ts @@ -1,5 +1,4 @@ import * as fs from "fs"; -import axios from "axios"; export async function readTestFile(filePath: string): Promise { if (isOnBrowserTests()) { @@ -22,7 +21,7 @@ export function isOnBrowserTests() { } export async function downloadTextFile(url: string) { - let response = await axios.get(url, { responseType: "text", transformResponse: [] }); - let text = response.data.toString(); + const response = await fetch(url); + const text = await response.text(); return text; } From f27408dfd0204f1b799cb0228c77a88a8ae2e4cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Thu, 21 Mar 2024 12:38:08 +0200 Subject: [PATCH 087/118] Accept "Uint8Array", in addition to "Buffer" on the main flows. --- src-wallet/signature.ts | 4 ++-- src-wallet/userKeys.ts | 4 ++-- src-wallet/userSigner.ts | 4 ++-- src-wallet/userVerifier.ts | 2 +- src-wallet/users.spec.ts | 19 +++++++++++++++---- src-wallet/validatorKeys.ts | 8 ++++---- src-wallet/validatorSigner.ts | 2 +- src-wallet/validators.spec.ts | 12 ++++++++++-- 8 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src-wallet/signature.ts b/src-wallet/signature.ts index ec10ea536..a99620ed3 100644 --- a/src-wallet/signature.ts +++ b/src-wallet/signature.ts @@ -4,8 +4,8 @@ export class Signature { private readonly buffer: Buffer; - constructor(buffer: Buffer) { - this.buffer = buffer; + constructor(buffer: Buffer | Uint8Array) { + this.buffer = Buffer.from(buffer); } hex() { diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index ba1177772..52aabf3e5 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -36,7 +36,7 @@ export class UserSecretKey { return new UserPublicKey(buffer); } - sign(message: Buffer): Buffer { + sign(message: Buffer | Uint8Array): Buffer { const signature = ed.sync.sign(new Uint8Array(message), new Uint8Array(this.buffer)); return Buffer.from(signature); } @@ -59,7 +59,7 @@ export class UserPublicKey { this.buffer = Buffer.from(buffer); } - verify(data: Buffer, signature: Buffer): boolean { + verify(data: Buffer | Uint8Array, signature: Buffer | Uint8Array): boolean { try { const ok = ed.sync.verify(new Uint8Array(signature), new Uint8Array(data), new Uint8Array(this.buffer)); return ok; diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index 7dfb52811..a66a0b57f 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -4,7 +4,7 @@ import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; interface IUserSecretKey { - sign(message: Buffer): Buffer; + sign(message: Buffer | Uint8Array): Buffer; generatePublicKey(): IUserPublicKey; } @@ -33,7 +33,7 @@ export class UserSigner { return new UserSigner(secretKey); } - async sign(data: Buffer): Promise { + async sign(data: Buffer | Uint8Array): Promise { try { const signature = this.secretKey.sign(data); return signature; diff --git a/src-wallet/userVerifier.ts b/src-wallet/userVerifier.ts index f9ff7cdeb..ca56585a0 100644 --- a/src-wallet/userVerifier.ts +++ b/src-wallet/userVerifier.ts @@ -25,7 +25,7 @@ export class UserVerifier { * @param signature the signature to be verified * @returns true if the signature is valid, false otherwise */ - verify(data: Buffer, signature: Buffer): boolean { + verify(data: Buffer | Uint8Array, signature: Buffer | Uint8Array): boolean { return this.publicKey.verify(data, signature); } } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 43cd1b464..6b2577ac1 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -43,11 +43,13 @@ describe("test user wallets", () => { }); it("should create secret key", () => { - let keyHex = alice.secretKeyHex; - let fromBuffer = new UserSecretKey(Buffer.from(keyHex, "hex")); - let fromHex = UserSecretKey.fromString(keyHex); + const keyHex = alice.secretKeyHex; + const fromBuffer = new UserSecretKey(Buffer.from(keyHex, "hex")); + const fromArray = new UserSecretKey(Uint8Array.from(Buffer.from(keyHex, "hex"))); + const fromHex = UserSecretKey.fromString(keyHex); assert.equal(fromBuffer.hex(), keyHex); + assert.equal(fromArray.hex(), keyHex); assert.equal(fromHex.hex(), keyHex); }); @@ -217,9 +219,11 @@ describe("test user wallets", () => { let serialized = transaction.serializeForSigning(); let signature = await signer.sign(serialized); + assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized))); assert.equal(serialized.toString(), `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); assert.equal(signature.toString("hex"), "a3b61a2fe461f3393c42e6cb0477a6b52ffd92168f10c111f6aa8d0a310ee0c314fae0670f8313f1ad992933ac637c61a8ff20cc20b6a8b2260a4af1a120a70d"); assert.isTrue(verifier.verify(serialized, signature)); + // Without data field transaction = new TestTransaction({ nonce: 8, @@ -233,6 +237,7 @@ describe("test user wallets", () => { serialized = transaction.serializeForSigning(); signature = await signer.sign(serialized); + assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized))); assert.equal(serialized.toString(), `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); assert.equal(signature.toString("hex"), "f136c901d37349a7da8cfe3ab5ec8ef333b0bc351517c0e9bef9eb9704aed3077bf222769cade5ff29dffe5f42e4f0c5e0b068bdba90cd2cb41da51fd45d5a03"); }); @@ -304,7 +309,10 @@ describe("test user wallets", () => { chainID: "1" }); - const signature = await signer.sign(transaction.serializeForSigning()); + const serialized = transaction.serializeForSigning(); + const signature = await signer.sign(serialized); + + assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized))); assert.equal(signature.toString("hex"), "ba4fa95fea1402e4876abf1d5a510615aab374ee48bb76f5230798a7d3f2fcae6ba91ba56c6d62e6e7003ce531ff02f219cb7218dd00dd2ca650ba747f19640a"); }); @@ -320,8 +328,11 @@ describe("test user wallets", () => { const data = message.serializeForSigning(); const signature = await signer.sign(data); + assert.deepEqual(await signer.sign(data), await signer.sign(Uint8Array.from(data))); assert.isTrue(verifier.verify(data, signature)); + assert.isTrue(verifier.verify(Uint8Array.from(data), Uint8Array.from(signature))); assert.isFalse(verifier.verify(Buffer.from("hello"), signature)); + assert.isFalse(verifier.verify(new TextEncoder().encode("hello"), signature)); }); it("should create UserSigner from wallet", async function () { diff --git a/src-wallet/validatorKeys.ts b/src-wallet/validatorKeys.ts index bd77ae2f9..3c3497622 100644 --- a/src-wallet/validatorKeys.ts +++ b/src-wallet/validatorKeys.ts @@ -31,7 +31,7 @@ export class ValidatorSecretKey { private readonly secretKey: any; private readonly publicKey: any; - constructor(buffer: Buffer) { + constructor(buffer: Buffer | Uint8Array) { BLS.guardInitialized(); guardLength(buffer, VALIDATOR_SECRETKEY_LENGTH); @@ -49,7 +49,7 @@ export class ValidatorSecretKey { return new ValidatorPublicKey(buffer); } - sign(message: Buffer): Buffer { + sign(message: Buffer | Uint8Array): Buffer { let signatureObject = this.secretKey.sign(message); let signature = Buffer.from(signatureObject.serialize()); return signature; @@ -67,10 +67,10 @@ export class ValidatorSecretKey { export class ValidatorPublicKey { private readonly buffer: Buffer; - constructor(buffer: Buffer) { + constructor(buffer: Buffer | Uint8Array) { guardLength(buffer, VALIDATOR_PUBKEY_LENGTH); - this.buffer = buffer; + this.buffer = Buffer.from(buffer); } hex(): string { diff --git a/src-wallet/validatorSigner.ts b/src-wallet/validatorSigner.ts index 92e4e2249..8c0c028f8 100644 --- a/src-wallet/validatorSigner.ts +++ b/src-wallet/validatorSigner.ts @@ -8,7 +8,7 @@ export class ValidatorSigner { /** * Signs a message. */ - async signUsingPem(pemText: string, pemIndex: number = 0, signable: Buffer): Promise { + async signUsingPem(pemText: string, pemIndex: number = 0, signable: Buffer | Uint8Array): Promise { await BLS.initIfNecessary(); try { diff --git a/src-wallet/validators.spec.ts b/src-wallet/validators.spec.ts index dee84cf8f..5a01b7a45 100644 --- a/src-wallet/validators.spec.ts +++ b/src-wallet/validators.spec.ts @@ -8,16 +8,24 @@ describe("test validator keys", () => { let secretKey = Buffer.from(Buffer.from("N2NmZjk5YmQ2NzE1MDJkYjdkMTViYzhhYmMwYzlhODA0ZmI5MjU0MDZmYmRkNTBmMWU0YzE3YTRjZDc3NDI0Nw==", "base64").toString(), "hex"); let key = new ValidatorSecretKey(secretKey); + + assert.deepEqual(new ValidatorSecretKey(secretKey), new ValidatorSecretKey(Uint8Array.from(secretKey))); assert.equal(key.generatePublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); - let signature = key.sign(Buffer.from("hello")); + const data = Buffer.from("hello"); + + let signature = key.sign(data); + + assert.deepEqual(key.sign(data), key.sign(Uint8Array.from(data))); assert.equal(signature.toString("hex"), "84fd0a3a9d4f1ea2d4b40c6da67f9b786284a1c3895b7253fec7311597cda3f757862bb0690a92a13ce612c33889fd86"); secretKey = Buffer.from(Buffer.from("ODA4NWJhMWQ3ZjdjM2RiOTM4YWQ3MDU5NWEyYmRhYjA5NjQ0ZjFlYzM4MDNiZTE3MWMzM2YxNGJjODBkNGUzYg==", "base64").toString(), "hex"); key = new ValidatorSecretKey(secretKey); assert.equal(key.generatePublicKey().hex(), "78689fd4b1e2e434d567fe01e61598a42717d83124308266bd09ccc15d2339dd318c019914b86ac29adbae5dd8a02d0307425e9bd85a296e94943708c72f8c670f0b7c50a890a5719088dbd9f1d062cad9acffa06df834106eebe1a4257ef00d"); - signature = key.sign(Buffer.from("hello")); + signature = key.sign(data); + + assert.deepEqual(key.sign(data), key.sign(Uint8Array.from(data))); assert.equal(signature.toString("hex"), "be2e593ff10899a2ee8e1d5c8094e36c9f48e04b87e129991ff09475808743e07bb41bf6e7bc1463fa554c4b46594b98"); }); From cac0eee01f96726bcbf30a26c907f0b4a58cf284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 14 May 2024 16:02:19 +0300 Subject: [PATCH 088/118] Sketch support for custom HRP. --- src-wallet/userAddress.ts | 21 +++++++++++++++------ src-wallet/userKeys.ts | 4 ++-- src-wallet/userSigner.ts | 7 +++---- src-wallet/userWallet.ts | 8 ++++---- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src-wallet/userAddress.ts b/src-wallet/userAddress.ts index 3ed7efe8f..56c879c0c 100644 --- a/src-wallet/userAddress.ts +++ b/src-wallet/userAddress.ts @@ -4,16 +4,18 @@ import { ErrBadAddress } from "./errors"; /** * The human-readable-part of the bech32 addresses. */ -const HRP = "erd"; +const DEFAULT_HRP = "erd"; /** * A user Address, as an immutable object. */ export class UserAddress { private readonly buffer: Buffer; + private readonly hrp: string; - public constructor(buffer: Buffer) { + public constructor(buffer: Buffer, hrp?: string) { this.buffer = buffer; + this.hrp = hrp || DEFAULT_HRP; } static fromBech32(value: string): UserAddress { @@ -25,12 +27,12 @@ export class UserAddress { throw new ErrBadAddress(value, err); } - if (decoded.prefix != HRP) { + if (decoded.prefix != DEFAULT_HRP) { throw new ErrBadAddress(value); } let pubkey = Buffer.from(bech32.fromWords(decoded.words)); - return new UserAddress(pubkey); + return new UserAddress(pubkey, decoded.prefix); } /** @@ -44,8 +46,8 @@ export class UserAddress { * Returns the bech32 representation of the address */ bech32(): string { - let words = bech32.toWords(this.pubkey()); - let address = bech32.encode(HRP, words); + const words = bech32.toWords(this.pubkey()); + const address = bech32.encode(this.hrp, words); return address; } @@ -56,6 +58,13 @@ export class UserAddress { return this.buffer; } + /** + * Returns the human-readable-part of the bech32 addresses. + */ + getHrp(): string { + return this.hrp; + } + /** * Returns the bech32 representation of the address */ diff --git a/src-wallet/userKeys.ts b/src-wallet/userKeys.ts index 52aabf3e5..6acb76f00 100644 --- a/src-wallet/userKeys.ts +++ b/src-wallet/userKeys.ts @@ -73,8 +73,8 @@ export class UserPublicKey { return this.buffer.toString("hex"); } - toAddress(): UserAddress { - return new UserAddress(this.buffer); + toAddress(hrp?: string): UserAddress { + return new UserAddress(this.buffer, hrp); } valueOf(): Buffer { diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index a66a0b57f..ff5bb642f 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -9,10 +9,9 @@ interface IUserSecretKey { } interface IUserPublicKey { - toAddress(): { bech32(): string; }; + toAddress(hrp?: string): { bech32(): string; }; } - /** * ed25519 signer */ @@ -45,8 +44,8 @@ export class UserSigner { /** * Gets the address of the signer. */ - getAddress(): UserAddress { - const bech32 = this.secretKey.generatePublicKey().toAddress().bech32(); + getAddress(hrp?: string): UserAddress { + const bech32 = this.secretKey.generatePublicKey().toAddress(hrp).bech32(); return UserAddress.fromBech32(bech32); } } diff --git a/src-wallet/userWallet.ts b/src-wallet/userWallet.ts index 2a0f04480..8c7ff833b 100644 --- a/src-wallet/userWallet.ts +++ b/src-wallet/userWallet.ts @@ -146,15 +146,15 @@ export class UserWallet { /** * Converts the encrypted keyfile to plain JavaScript object. */ - toJSON(): any { + toJSON(addressHrp?: string): any { if (this.kind == UserWalletKind.SecretKey) { - return this.toJSONWhenKindIsSecretKey(); + return this.toJSONWhenKindIsSecretKey(addressHrp); } return this.toJSONWhenKindIsMnemonic(); } - private toJSONWhenKindIsSecretKey(): any { + private toJSONWhenKindIsSecretKey(addressHrp?: string): any { if (!this.publicKeyWhenKindIsSecretKey) { throw new Err("Public key isn't available"); } @@ -166,7 +166,7 @@ export class UserWallet { kind: this.kind, id: this.encryptedData.id, address: this.publicKeyWhenKindIsSecretKey.hex(), - bech32: this.publicKeyWhenKindIsSecretKey.toAddress().toString(), + bech32: this.publicKeyWhenKindIsSecretKey.toAddress(addressHrp).toString(), crypto: cryptoSection }; From 3b4400ee7ce069d93e9a047efaeb455ad328a265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 14 May 2024 16:12:21 +0300 Subject: [PATCH 089/118] Add "newFromBech32", in addition to the legacy "fromBech32". --- src-wallet/userAddress.ts | 52 ++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/src-wallet/userAddress.ts b/src-wallet/userAddress.ts index 56c879c0c..9db527cc1 100644 --- a/src-wallet/userAddress.ts +++ b/src-wallet/userAddress.ts @@ -18,21 +18,21 @@ export class UserAddress { this.hrp = hrp || DEFAULT_HRP; } - static fromBech32(value: string): UserAddress { - let decoded; - - try { - decoded = bech32.decode(value); - } catch (err: any) { - throw new ErrBadAddress(value, err); - } - - if (decoded.prefix != DEFAULT_HRP) { - throw new ErrBadAddress(value); - } + /** + * Creates an address object from a bech32-encoded string + */ + static newFromBech32(value: string): UserAddress { + const { hrp, pubkey } = decodeFromBech32({ value, allowCustomHrp: true }); + return new UserAddress(pubkey, hrp); + } - let pubkey = Buffer.from(bech32.fromWords(decoded.words)); - return new UserAddress(pubkey, decoded.prefix); + /** + * Use {@link newFromBech32} instead. + */ + static fromBech32(value: string): UserAddress { + // On this legacy flow, we do not accept addresses with custom hrp (in order to avoid behavioral breaking changes). + const { hrp, pubkey } = decodeFromBech32({ value, allowCustomHrp: false }); + return new UserAddress(pubkey, hrp); } /** @@ -82,3 +82,27 @@ export class UserAddress { }; } } + +function decodeFromBech32(options: { value: string; allowCustomHrp: boolean }): { hrp: string; pubkey: Buffer } { + const value = options.value; + const allowCustomHrp = options.allowCustomHrp; + + let hrp: string; + let pubkey: Buffer; + + try { + const decoded = bech32.decode(value); + + hrp = decoded.prefix; + pubkey = Buffer.from(bech32.fromWords(decoded.words)); + } catch (err: any) { + throw new ErrBadAddress(value, err); + } + + // Workaround, in order to avoid behavioral breaking changes on legacy flows. + if (!allowCustomHrp && hrp != DEFAULT_HRP) { + throw new ErrBadAddress(value); + } + + return { hrp, pubkey }; +} From ee264ef1a872d6cb64ef9dc0d59891b93c07d4dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 14 May 2024 17:25:22 +0300 Subject: [PATCH 090/118] Undo some changes, add "internal" markers, add some tests. --- src-wallet/userAddress.ts | 18 +++++------------- src-wallet/userSigner.ts | 2 +- src-wallet/users.spec.ts | 8 ++++++++ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src-wallet/userAddress.ts b/src-wallet/userAddress.ts index 9db527cc1..ffeb9e962 100644 --- a/src-wallet/userAddress.ts +++ b/src-wallet/userAddress.ts @@ -7,7 +7,8 @@ import { ErrBadAddress } from "./errors"; const DEFAULT_HRP = "erd"; /** - * A user Address, as an immutable object. + * @internal + * For internal use only. */ export class UserAddress { private readonly buffer: Buffer; @@ -18,16 +19,14 @@ export class UserAddress { this.hrp = hrp || DEFAULT_HRP; } - /** - * Creates an address object from a bech32-encoded string - */ static newFromBech32(value: string): UserAddress { const { hrp, pubkey } = decodeFromBech32({ value, allowCustomHrp: true }); return new UserAddress(pubkey, hrp); } /** - * Use {@link newFromBech32} instead. + * @internal + * For internal use only. */ static fromBech32(value: string): UserAddress { // On this legacy flow, we do not accept addresses with custom hrp (in order to avoid behavioral breaking changes). @@ -57,14 +56,7 @@ export class UserAddress { pubkey(): Buffer { return this.buffer; } - - /** - * Returns the human-readable-part of the bech32 addresses. - */ - getHrp(): string { - return this.hrp; - } - + /** * Returns the bech32 representation of the address */ diff --git a/src-wallet/userSigner.ts b/src-wallet/userSigner.ts index ff5bb642f..b78d5048d 100644 --- a/src-wallet/userSigner.ts +++ b/src-wallet/userSigner.ts @@ -46,6 +46,6 @@ export class UserSigner { */ getAddress(hrp?: string): UserAddress { const bech32 = this.secretKey.generatePublicKey().toAddress(hrp).bech32(); - return UserAddress.fromBech32(bech32); + return UserAddress.newFromBech32(bech32); } } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 6b2577ac1..41a1aa30e 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -40,6 +40,10 @@ describe("test user wallets", () => { assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), "erd1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqg4g7na"); assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), "erd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqnts4zs09p"); assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), "erd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksptewtj"); + + assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress("test").bech32(), "test1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqc6tnnf"); + assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress("xerd").bech32(), "xerd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqntsj4adj4"); + assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress("yerd").bech32(), "yerd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksn8p0n5"); }); it("should create secret key", () => { @@ -346,6 +350,10 @@ describe("test user wallets", () => { assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + + assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress("test").bech32(), "test1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ss5hqhtr"); + assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress("xerd").bech32(), "xerd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruq9thc9j"); + assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress("yerd").bech32(), "yerd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaqgh23pp"); }); it("should throw error when decrypting secret key with keystore-mnemonic file", async function () { From 01b9715662274624513d0e0dcd689429faf928a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 14 May 2024 17:27:41 +0300 Subject: [PATCH 091/118] Deprecation notice. --- src-wallet/userAddress.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-wallet/userAddress.ts b/src-wallet/userAddress.ts index ffeb9e962..0c5943937 100644 --- a/src-wallet/userAddress.ts +++ b/src-wallet/userAddress.ts @@ -26,7 +26,7 @@ export class UserAddress { /** * @internal - * For internal use only. + * @deprecated */ static fromBech32(value: string): UserAddress { // On this legacy flow, we do not accept addresses with custom hrp (in order to avoid behavioral breaking changes). From 26ee76a14df2929e11c294ea1d3aee3af016d279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Wed, 15 May 2024 14:14:52 +0300 Subject: [PATCH 092/118] Add library config. --- src-wallet/config.ts | 15 +++++++++++++++ src-wallet/userAddress.ts | 10 +++------- 2 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 src-wallet/config.ts diff --git a/src-wallet/config.ts b/src-wallet/config.ts new file mode 100644 index 000000000..cf60ce5a4 --- /dev/null +++ b/src-wallet/config.ts @@ -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"; +} diff --git a/src-wallet/userAddress.ts b/src-wallet/userAddress.ts index 0c5943937..b7a885cf8 100644 --- a/src-wallet/userAddress.ts +++ b/src-wallet/userAddress.ts @@ -1,11 +1,7 @@ import * as bech32 from "bech32"; +import { LibraryConfig } from "./config"; import { ErrBadAddress } from "./errors"; -/** - * The human-readable-part of the bech32 addresses. - */ -const DEFAULT_HRP = "erd"; - /** * @internal * For internal use only. @@ -16,7 +12,7 @@ export class UserAddress { public constructor(buffer: Buffer, hrp?: string) { this.buffer = buffer; - this.hrp = hrp || DEFAULT_HRP; + this.hrp = hrp || LibraryConfig.DefaultAddressHrp; } static newFromBech32(value: string): UserAddress { @@ -92,7 +88,7 @@ function decodeFromBech32(options: { value: string; allowCustomHrp: boolean }): } // Workaround, in order to avoid behavioral breaking changes on legacy flows. - if (!allowCustomHrp && hrp != DEFAULT_HRP) { + if (!allowCustomHrp && hrp != LibraryConfig.DefaultAddressHrp) { throw new ErrBadAddress(value); } From 5352a6ddc70dc6ef0c533074e95b4a532a4d4e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 1 Oct 2024 11:50:15 +0300 Subject: [PATCH 093/118] Add mnemonicToEntropy and entropyToMnemonic. --- src-wallet/mnemonic.ts | 20 ++++++++++++++++++-- src-wallet/users.spec.ts | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts index ed5423b26..de6c992d7 100644 --- a/src-wallet/mnemonic.ts +++ b/src-wallet/mnemonic.ts @@ -1,4 +1,10 @@ -import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from "bip39"; +import { + entropyToMnemonic, + generateMnemonic, + mnemonicToEntropy, + mnemonicToSeedSync, + validateMnemonic, +} from "bip39"; import { derivePath } from "ed25519-hd-key"; import { ErrWrongMnemonic } from "./errors"; import { UserSecretKey } from "./userKeys"; @@ -14,7 +20,7 @@ export class Mnemonic { } static generate(): Mnemonic { - let text = generateMnemonic(MNEMONIC_STRENGTH); + const text = generateMnemonic(MNEMONIC_STRENGTH); return new Mnemonic(text); } @@ -25,6 +31,11 @@ export class Mnemonic { return new Mnemonic(text); } + static fromEntropy(entropy: Uint8Array): Mnemonic { + const text = entropyToMnemonic(Buffer.from(entropy)); + return new Mnemonic(text); + } + public static assertTextIsValid(text: string) { let isValid = validateMnemonic(text); @@ -45,6 +56,11 @@ export class Mnemonic { return this.text.split(" "); } + getEntropy(): Uint8Array { + const entropy = mnemonicToEntropy(this.text); + return Buffer.from(entropy, "hex"); + } + toString(): string { return this.text; } diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 41a1aa30e..84eb6ab98 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -26,6 +26,26 @@ describe("test user wallets", () => { assert.lengthOf(words, 24); }); + it("should convert entropy to mnemonic and back", () => { + function testConversion(text: string, entropyHex: string) { + const entropyFromMnemonic = Mnemonic.fromString(text).getEntropy(); + const mnemonicFromEntropy = Mnemonic.fromEntropy(Buffer.from(entropyHex, "hex")); + + assert.equal(Buffer.from(entropyFromMnemonic).toString("hex"), entropyHex); + assert.equal(mnemonicFromEntropy.toString(), text); + } + + testConversion( + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + "00000000000000000000000000000000", + ); + + testConversion( + "moral volcano peasant pass circle pen over picture flat shop clap goat never lyrics gather prepare woman film husband gravity behind test tiger improve", + "8fbeb688d0529344e77d225898d4a73209510ad81d4ffceac9bfb30149bf387b", + ); + }); + it("should derive keys", async () => { let mnemonic = Mnemonic.fromString(DummyMnemonic); From a5e00175e9b10d296763f89e3b1f514468f20691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 1 Oct 2024 12:06:25 +0300 Subject: [PATCH 094/118] Handle some errors. --- src-wallet/errors.ts | 11 ++++++++++- src-wallet/mnemonic.ts | 8 ++++++-- src-wallet/users.spec.ts | 8 ++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src-wallet/errors.ts b/src-wallet/errors.ts index 19f1ac852..4c3ea2ecd 100644 --- a/src-wallet/errors.ts +++ b/src-wallet/errors.ts @@ -28,6 +28,15 @@ export class ErrWrongMnemonic extends Err { } } +/** + * Signals a bad mnemonic entropy. + */ +export class ErrBadMnemonicEntropy extends Err { + public constructor(inner: Error) { + super("Bad mnemonic entropy", inner); + } +} + /** * Signals a bad PEM file. */ @@ -49,7 +58,7 @@ export class ErrSignerCannotSign extends Err { /** * Signals a bad address. */ - export class ErrBadAddress extends Err { +export class ErrBadAddress extends Err { public constructor(value: string, inner?: Error) { super(`Bad address: ${value}`, inner); } diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts index de6c992d7..cb1c81ec1 100644 --- a/src-wallet/mnemonic.ts +++ b/src-wallet/mnemonic.ts @@ -32,8 +32,12 @@ export class Mnemonic { } static fromEntropy(entropy: Uint8Array): Mnemonic { - const text = entropyToMnemonic(Buffer.from(entropy)); - return new Mnemonic(text); + try { + const text = entropyToMnemonic(Buffer.from(entropy)); + return new Mnemonic(text); + } catch (err: any) { + throw new ErrBadMnemonicEntropy(err); + } } public static assertTextIsValid(text: string) { diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 84eb6ab98..864096ba4 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -44,6 +44,14 @@ describe("test user wallets", () => { "moral volcano peasant pass circle pen over picture flat shop clap goat never lyrics gather prepare woman film husband gravity behind test tiger improve", "8fbeb688d0529344e77d225898d4a73209510ad81d4ffceac9bfb30149bf387b", ); + + assert.throws( + () => { + Mnemonic.fromEntropy(Buffer.from("abba", "hex")); + }, + ErrBadMnemonicEntropy, + `Bad mnemonic entropy`, + ); }); it("should derive keys", async () => { From 2f74baa89b182fe280ae46dad8102bac756e1540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 1 Oct 2024 12:07:15 +0300 Subject: [PATCH 095/118] Fix import. --- src-wallet/mnemonic.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src-wallet/mnemonic.ts b/src-wallet/mnemonic.ts index cb1c81ec1..93b62ee3a 100644 --- a/src-wallet/mnemonic.ts +++ b/src-wallet/mnemonic.ts @@ -1,12 +1,6 @@ -import { - entropyToMnemonic, - generateMnemonic, - mnemonicToEntropy, - mnemonicToSeedSync, - validateMnemonic, -} from "bip39"; +import { entropyToMnemonic, generateMnemonic, mnemonicToEntropy, mnemonicToSeedSync, validateMnemonic } from "bip39"; import { derivePath } from "ed25519-hd-key"; -import { ErrWrongMnemonic } from "./errors"; +import { ErrBadMnemonicEntropy, ErrWrongMnemonic } from "./errors"; import { UserSecretKey } from "./userKeys"; const MNEMONIC_STRENGTH = 256; From 4fb5e5c06c497eb42738478f9515c64733e96ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20B=C4=83ncioiu?= Date: Tue, 1 Oct 2024 12:07:31 +0300 Subject: [PATCH 096/118] Fix imports. --- src-wallet/users.spec.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src-wallet/users.spec.ts b/src-wallet/users.spec.ts index 864096ba4..dc6d8d4fc 100644 --- a/src-wallet/users.spec.ts +++ b/src-wallet/users.spec.ts @@ -1,10 +1,17 @@ import { assert } from "chai"; import { Randomness } from "./crypto"; -import { ErrInvariantFailed } from "./errors"; +import { ErrBadMnemonicEntropy, ErrInvariantFailed } from "./errors"; import { Mnemonic } from "./mnemonic"; import { TestMessage } from "./testutils/message"; import { TestTransaction } from "./testutils/transaction"; -import { DummyMnemonic, DummyMnemonicOf12Words, DummyPassword, loadTestKeystore, loadTestWallet, TestWallet } from "./testutils/wallets"; +import { + DummyMnemonic, + DummyMnemonicOf12Words, + DummyPassword, + loadTestKeystore, + loadTestWallet, + TestWallet, +} from "./testutils/wallets"; import { UserSecretKey } from "./userKeys"; import { UserSigner } from "./userSigner"; import { UserVerifier } from "./userVerifier"; From cfaed6ed4b92b1f5cee67756d7b543dcb29877d2 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 3 Oct 2024 15:32:00 +0300 Subject: [PATCH 097/118] Update folder structure --- package-lock.json | 222 ++++++++++++------ package.json | 14 +- {src-wallet => src/wallet}/assertions.ts | 0 {src-wallet => src/wallet}/config.ts | 0 .../wallet}/crypto/constants.ts | 0 .../wallet}/crypto/decryptor.ts | 0 .../wallet}/crypto/derivationParams.ts | 0 .../wallet}/crypto/encrypt.spec.ts | 0 .../wallet}/crypto/encryptedData.ts | 0 .../wallet}/crypto/encryptor.ts | 0 {src-wallet => src/wallet}/crypto/index.ts | 0 .../wallet}/crypto/pubkeyDecryptor.ts | 0 .../wallet}/crypto/pubkeyEncrypt.spec.ts | 0 .../wallet}/crypto/pubkeyEncryptor.ts | 0 .../wallet}/crypto/randomness.ts | 0 .../wallet}/crypto/x25519EncryptedData.ts | 0 {src-wallet => src/wallet}/errors.ts | 0 {src-wallet => src/wallet}/index.ts | 0 {src-wallet => src/wallet}/mnemonic.ts | 0 {src-wallet => src/wallet}/pem.spec.ts | 0 {src-wallet => src/wallet}/pem.ts | 0 {src-wallet => src/wallet}/signature.ts | 0 .../wallet}/testdata/alice.json | 0 {src-wallet => src/wallet}/testdata/alice.pem | 0 {src-wallet => src/wallet}/testdata/bob.json | 0 {src-wallet => src/wallet}/testdata/bob.pem | 0 .../wallet}/testdata/carol.json | 0 {src-wallet => src/wallet}/testdata/carol.pem | 0 .../wallet}/testdata/withDummyMnemonic.json | 0 .../wallet}/testdata/withDummySecretKey.json | 0 .../wallet}/testdata/withoutKind.json | 0 {src-wallet => src/wallet}/testutils/files.ts | 0 .../wallet}/testutils/message.ts | 0 .../wallet}/testutils/transaction.ts | 0 .../wallet}/testutils/wallets.ts | 0 {src-wallet => src/wallet}/userAddress.ts | 0 {src-wallet => src/wallet}/userKeys.ts | 0 {src-wallet => src/wallet}/userSigner.ts | 0 {src-wallet => src/wallet}/userVerifier.ts | 0 {src-wallet => src/wallet}/userWallet.ts | 0 {src-wallet => src/wallet}/users.spec.ts | 0 .../wallet}/usersBenchmark.spec.ts | 0 {src-wallet => src/wallet}/validatorKeys.ts | 0 {src-wallet => src/wallet}/validatorSigner.ts | 0 {src-wallet => src/wallet}/validators.spec.ts | 0 45 files changed, 160 insertions(+), 76 deletions(-) rename {src-wallet => src/wallet}/assertions.ts (100%) rename {src-wallet => src/wallet}/config.ts (100%) rename {src-wallet => src/wallet}/crypto/constants.ts (100%) rename {src-wallet => src/wallet}/crypto/decryptor.ts (100%) rename {src-wallet => src/wallet}/crypto/derivationParams.ts (100%) rename {src-wallet => src/wallet}/crypto/encrypt.spec.ts (100%) rename {src-wallet => src/wallet}/crypto/encryptedData.ts (100%) rename {src-wallet => src/wallet}/crypto/encryptor.ts (100%) rename {src-wallet => src/wallet}/crypto/index.ts (100%) rename {src-wallet => src/wallet}/crypto/pubkeyDecryptor.ts (100%) rename {src-wallet => src/wallet}/crypto/pubkeyEncrypt.spec.ts (100%) rename {src-wallet => src/wallet}/crypto/pubkeyEncryptor.ts (100%) rename {src-wallet => src/wallet}/crypto/randomness.ts (100%) rename {src-wallet => src/wallet}/crypto/x25519EncryptedData.ts (100%) rename {src-wallet => src/wallet}/errors.ts (100%) rename {src-wallet => src/wallet}/index.ts (100%) rename {src-wallet => src/wallet}/mnemonic.ts (100%) rename {src-wallet => src/wallet}/pem.spec.ts (100%) rename {src-wallet => src/wallet}/pem.ts (100%) rename {src-wallet => src/wallet}/signature.ts (100%) rename {src-wallet => src/wallet}/testdata/alice.json (100%) rename {src-wallet => src/wallet}/testdata/alice.pem (100%) rename {src-wallet => src/wallet}/testdata/bob.json (100%) rename {src-wallet => src/wallet}/testdata/bob.pem (100%) rename {src-wallet => src/wallet}/testdata/carol.json (100%) rename {src-wallet => src/wallet}/testdata/carol.pem (100%) rename {src-wallet => src/wallet}/testdata/withDummyMnemonic.json (100%) rename {src-wallet => src/wallet}/testdata/withDummySecretKey.json (100%) rename {src-wallet => src/wallet}/testdata/withoutKind.json (100%) rename {src-wallet => src/wallet}/testutils/files.ts (100%) rename {src-wallet => src/wallet}/testutils/message.ts (100%) rename {src-wallet => src/wallet}/testutils/transaction.ts (100%) rename {src-wallet => src/wallet}/testutils/wallets.ts (100%) rename {src-wallet => src/wallet}/userAddress.ts (100%) rename {src-wallet => src/wallet}/userKeys.ts (100%) rename {src-wallet => src/wallet}/userSigner.ts (100%) rename {src-wallet => src/wallet}/userVerifier.ts (100%) rename {src-wallet => src/wallet}/userWallet.ts (100%) rename {src-wallet => src/wallet}/users.spec.ts (100%) rename {src-wallet => src/wallet}/usersBenchmark.spec.ts (100%) rename {src-wallet => src/wallet}/validatorKeys.ts (100%) rename {src-wallet => src/wallet}/validatorSigner.ts (100%) rename {src-wallet => src/wallet}/validators.spec.ts (100%) diff --git a/package-lock.json b/package-lock.json index 7821f4817..5aa4414f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,19 +9,31 @@ "version": "13.7.0", "license": "MIT", "dependencies": { + "@multiversx/sdk-bls-wasm": "0.3.5", "@multiversx/sdk-transaction-decoder": "1.0.2", + "@noble/ed25519": "1.7.3", + "@noble/hashes": "1.3.0", "bech32": "1.1.4", + "bip39": "3.1.0", "blake2b": "2.1.3", "buffer": "6.0.3", + "ed25519-hd-key": "1.1.2", + "ed2curve": "0.3.0", "json-bigint": "1.0.0", - "keccak": "3.0.2" + "keccak": "3.0.2", + "scryptsy": "2.1.0", + "tweetnacl": "1.0.3", + "uuid": "8.3.2" }, "devDependencies": { "@multiversx/sdk-wallet": "4.5.1", "@types/assert": "1.4.6", "@types/chai": "4.2.11", + "@types/ed2curve": "0.2.2", "@types/mocha": "9.1.0", "@types/node": "13.13.2", + "@types/scryptsy": "2.0.0", + "@types/uuid": "8.3.0", "@typescript-eslint/eslint-plugin": "5.44.0", "@typescript-eslint/parser": "5.44.0", "assert": "2.0.0", @@ -155,7 +167,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@multiversx/sdk-bls-wasm/-/sdk-bls-wasm-0.3.5.tgz", "integrity": "sha512-c0tIdQUnbBLSt6NYU+OpeGPYdL0+GV547HeHT8Xc0BKQ7Cj0v82QUoA2QRtWrR1G4MNZmLsIacZSsf6DrIS2Bw==", - "dev": true, "engines": { "node": ">=8.9.0" } @@ -193,6 +204,24 @@ "uuid": "8.3.2" } }, + "node_modules/@multiversx/sdk-wallet/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", + "dev": true + }, + "node_modules/@multiversx/sdk-wallet/node_modules/bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "dev": true, + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, "node_modules/@multiversx/sdk-wallet/node_modules/keccak": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", @@ -211,7 +240,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==", - "dev": true, "funding": [ { "type": "individual", @@ -223,7 +251,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", - "dev": true, "funding": [ { "type": "individual", @@ -342,6 +369,15 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, + "node_modules/@types/ed2curve": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.2.tgz", + "integrity": "sha512-G1sTX5xo91ydevQPINbL2nfgVAj/s1ZiqZxC8OCWduwu+edoNGUm5JXtTkg9F3LsBZbRI46/0HES4CPUE2wc9g==", + "dev": true, + "dependencies": { + "tweetnacl": "^1.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -381,12 +417,27 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==" }, + "node_modules/@types/scryptsy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/scryptsy/-/scryptsy-2.0.0.tgz", + "integrity": "sha512-iDmneBKWSsmsR3SlGisOVnpCz7sB5Mqhv4I/pLg1DYq2zttWT65r+1gOVZtoxXiTsSf/JO8NhgyCKhx7cvGbaA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "node_modules/@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.44.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz", @@ -949,23 +1000,13 @@ } }, "node_modules/bip39": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", - "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", "dependencies": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" + "@noble/hashes": "^1.2.0" } }, - "node_modules/bip39/node_modules/@types/node": { - "version": "11.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", - "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", - "dev": true - }, "node_modules/blake2b": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/blake2b/-/blake2b-2.1.3.tgz", @@ -1392,7 +1433,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -1498,7 +1538,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -1511,7 +1550,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -1765,18 +1803,32 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/ed25519-hd-key/-/ed25519-hd-key-1.1.2.tgz", "integrity": "sha512-/0y9y6N7vM6Kj5ASr9J9wcMVDTtygxSOvYX+PJiMD7VcxCx2G03V5bLRl8Dug9EgkLFsLhGqBtQWQRcElEeWTA==", - "dev": true, "dependencies": { "bip39": "3.0.2", "create-hmac": "1.1.7", "tweetnacl": "1.0.3" } }, + "node_modules/ed25519-hd-key/node_modules/@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + }, + "node_modules/ed25519-hd-key/node_modules/bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "dependencies": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, "node_modules/ed2curve": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz", "integrity": "sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==", - "dev": true, "dependencies": { "tweetnacl": "1.x.x" } @@ -2646,7 +2698,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, "dependencies": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -2660,7 +2711,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3384,7 +3434,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -3944,7 +3993,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, "dependencies": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -4277,7 +4325,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -4428,7 +4475,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -4485,8 +4531,7 @@ "node_modules/scryptsy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", - "dev": true + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" }, "node_modules/serialize-javascript": { "version": "6.0.0", @@ -4501,7 +4546,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -4857,8 +4901,7 @@ "node_modules/tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, "node_modules/type-check": { "version": "0.4.0", @@ -5017,7 +5060,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -5298,8 +5340,7 @@ "@multiversx/sdk-bls-wasm": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@multiversx/sdk-bls-wasm/-/sdk-bls-wasm-0.3.5.tgz", - "integrity": "sha512-c0tIdQUnbBLSt6NYU+OpeGPYdL0+GV547HeHT8Xc0BKQ7Cj0v82QUoA2QRtWrR1G4MNZmLsIacZSsf6DrIS2Bw==", - "dev": true + "integrity": "sha512-c0tIdQUnbBLSt6NYU+OpeGPYdL0+GV547HeHT8Xc0BKQ7Cj0v82QUoA2QRtWrR1G4MNZmLsIacZSsf6DrIS2Bw==" }, "@multiversx/sdk-transaction-decoder": { "version": "1.0.2", @@ -5336,6 +5377,24 @@ "uuid": "8.3.2" }, "dependencies": { + "@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", + "dev": true + }, + "bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "dev": true, + "requires": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, "keccak": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", @@ -5351,14 +5410,12 @@ "@noble/ed25519": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", - "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==", - "dev": true + "integrity": "sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ==" }, "@noble/hashes": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz", - "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==", - "dev": true + "integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -5462,6 +5519,15 @@ "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", "dev": true }, + "@types/ed2curve": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/ed2curve/-/ed2curve-0.2.2.tgz", + "integrity": "sha512-G1sTX5xo91ydevQPINbL2nfgVAj/s1ZiqZxC8OCWduwu+edoNGUm5JXtTkg9F3LsBZbRI46/0HES4CPUE2wc9g==", + "dev": true, + "requires": { + "tweetnacl": "^1.0.0" + } + }, "@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5501,12 +5567,27 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==" }, + "@types/scryptsy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/scryptsy/-/scryptsy-2.0.0.tgz", + "integrity": "sha512-iDmneBKWSsmsR3SlGisOVnpCz7sB5Mqhv4I/pLg1DYq2zttWT65r+1gOVZtoxXiTsSf/JO8NhgyCKhx7cvGbaA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/semver": { "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", "dev": true }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "5.44.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz", @@ -5889,23 +5970,11 @@ "dev": true }, "bip39": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", - "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", "requires": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - }, - "dependencies": { - "@types/node": { - "version": "11.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", - "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", - "dev": true - } + "@noble/hashes": "^1.2.0" } }, "blake2b": { @@ -6276,7 +6345,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -6378,7 +6446,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -6391,7 +6458,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -6590,18 +6656,34 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/ed25519-hd-key/-/ed25519-hd-key-1.1.2.tgz", "integrity": "sha512-/0y9y6N7vM6Kj5ASr9J9wcMVDTtygxSOvYX+PJiMD7VcxCx2G03V5bLRl8Dug9EgkLFsLhGqBtQWQRcElEeWTA==", - "dev": true, "requires": { "bip39": "3.0.2", "create-hmac": "1.1.7", "tweetnacl": "1.0.3" + }, + "dependencies": { + "@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + }, + "bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "requires": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + } } }, "ed2curve": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz", "integrity": "sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==", - "dev": true, "requires": { "tweetnacl": "1.x.x" } @@ -7243,7 +7325,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, "requires": { "inherits": "^2.0.4", "readable-stream": "^3.6.0", @@ -7254,7 +7335,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7802,7 +7882,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -8235,7 +8314,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -8474,7 +8552,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -8596,7 +8673,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -8625,8 +8701,7 @@ "scryptsy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", - "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", - "dev": true + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" }, "serialize-javascript": { "version": "6.0.0", @@ -8641,7 +8716,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -8922,8 +8996,7 @@ "tweetnacl": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, "type-check": { "version": "0.4.0", @@ -9051,8 +9124,7 @@ "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "vm-browserify": { "version": "1.1.2", diff --git a/package.json b/package.json index 1fe34dd72..061c1ab0a 100644 --- a/package.json +++ b/package.json @@ -37,18 +37,29 @@ }, "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", + "@multiversx/sdk-bls-wasm": "0.3.5", "json-bigint": "1.0.0", "bech32": "1.1.4", + "bip39": "3.1.0", "blake2b": "2.1.3", "buffer": "6.0.3", - "keccak": "3.0.2" + "ed25519-hd-key": "1.1.2", + "ed2curve": "0.3.0", + "keccak": "3.0.2", + "scryptsy": "2.1.0", + "tweetnacl": "1.0.3", + "@noble/ed25519": "1.7.3", + "@noble/hashes": "1.3.0", + "uuid": "8.3.2" }, "devDependencies": { "@multiversx/sdk-wallet": "4.5.1", "@types/assert": "1.4.6", "@types/chai": "4.2.11", + "@types/ed2curve": "0.2.2", "@types/mocha": "9.1.0", "@types/node": "13.13.2", + "@types/scryptsy": "2.0.0", "assert": "2.0.0", "browserify": "17.0.0", "chai": "4.2.0", @@ -58,6 +69,7 @@ "typescript": "4.1.2", "@typescript-eslint/eslint-plugin": "5.44.0", "@typescript-eslint/parser": "5.44.0", + "@types/uuid": "8.3.0", "eslint": "8.28.0", "eslint-config-prettier": "9.1.0", "prettier": "3.2.4" diff --git a/src-wallet/assertions.ts b/src/wallet/assertions.ts similarity index 100% rename from src-wallet/assertions.ts rename to src/wallet/assertions.ts diff --git a/src-wallet/config.ts b/src/wallet/config.ts similarity index 100% rename from src-wallet/config.ts rename to src/wallet/config.ts diff --git a/src-wallet/crypto/constants.ts b/src/wallet/crypto/constants.ts similarity index 100% rename from src-wallet/crypto/constants.ts rename to src/wallet/crypto/constants.ts diff --git a/src-wallet/crypto/decryptor.ts b/src/wallet/crypto/decryptor.ts similarity index 100% rename from src-wallet/crypto/decryptor.ts rename to src/wallet/crypto/decryptor.ts diff --git a/src-wallet/crypto/derivationParams.ts b/src/wallet/crypto/derivationParams.ts similarity index 100% rename from src-wallet/crypto/derivationParams.ts rename to src/wallet/crypto/derivationParams.ts diff --git a/src-wallet/crypto/encrypt.spec.ts b/src/wallet/crypto/encrypt.spec.ts similarity index 100% rename from src-wallet/crypto/encrypt.spec.ts rename to src/wallet/crypto/encrypt.spec.ts diff --git a/src-wallet/crypto/encryptedData.ts b/src/wallet/crypto/encryptedData.ts similarity index 100% rename from src-wallet/crypto/encryptedData.ts rename to src/wallet/crypto/encryptedData.ts diff --git a/src-wallet/crypto/encryptor.ts b/src/wallet/crypto/encryptor.ts similarity index 100% rename from src-wallet/crypto/encryptor.ts rename to src/wallet/crypto/encryptor.ts diff --git a/src-wallet/crypto/index.ts b/src/wallet/crypto/index.ts similarity index 100% rename from src-wallet/crypto/index.ts rename to src/wallet/crypto/index.ts diff --git a/src-wallet/crypto/pubkeyDecryptor.ts b/src/wallet/crypto/pubkeyDecryptor.ts similarity index 100% rename from src-wallet/crypto/pubkeyDecryptor.ts rename to src/wallet/crypto/pubkeyDecryptor.ts diff --git a/src-wallet/crypto/pubkeyEncrypt.spec.ts b/src/wallet/crypto/pubkeyEncrypt.spec.ts similarity index 100% rename from src-wallet/crypto/pubkeyEncrypt.spec.ts rename to src/wallet/crypto/pubkeyEncrypt.spec.ts diff --git a/src-wallet/crypto/pubkeyEncryptor.ts b/src/wallet/crypto/pubkeyEncryptor.ts similarity index 100% rename from src-wallet/crypto/pubkeyEncryptor.ts rename to src/wallet/crypto/pubkeyEncryptor.ts diff --git a/src-wallet/crypto/randomness.ts b/src/wallet/crypto/randomness.ts similarity index 100% rename from src-wallet/crypto/randomness.ts rename to src/wallet/crypto/randomness.ts diff --git a/src-wallet/crypto/x25519EncryptedData.ts b/src/wallet/crypto/x25519EncryptedData.ts similarity index 100% rename from src-wallet/crypto/x25519EncryptedData.ts rename to src/wallet/crypto/x25519EncryptedData.ts diff --git a/src-wallet/errors.ts b/src/wallet/errors.ts similarity index 100% rename from src-wallet/errors.ts rename to src/wallet/errors.ts diff --git a/src-wallet/index.ts b/src/wallet/index.ts similarity index 100% rename from src-wallet/index.ts rename to src/wallet/index.ts diff --git a/src-wallet/mnemonic.ts b/src/wallet/mnemonic.ts similarity index 100% rename from src-wallet/mnemonic.ts rename to src/wallet/mnemonic.ts diff --git a/src-wallet/pem.spec.ts b/src/wallet/pem.spec.ts similarity index 100% rename from src-wallet/pem.spec.ts rename to src/wallet/pem.spec.ts diff --git a/src-wallet/pem.ts b/src/wallet/pem.ts similarity index 100% rename from src-wallet/pem.ts rename to src/wallet/pem.ts diff --git a/src-wallet/signature.ts b/src/wallet/signature.ts similarity index 100% rename from src-wallet/signature.ts rename to src/wallet/signature.ts diff --git a/src-wallet/testdata/alice.json b/src/wallet/testdata/alice.json similarity index 100% rename from src-wallet/testdata/alice.json rename to src/wallet/testdata/alice.json diff --git a/src-wallet/testdata/alice.pem b/src/wallet/testdata/alice.pem similarity index 100% rename from src-wallet/testdata/alice.pem rename to src/wallet/testdata/alice.pem diff --git a/src-wallet/testdata/bob.json b/src/wallet/testdata/bob.json similarity index 100% rename from src-wallet/testdata/bob.json rename to src/wallet/testdata/bob.json diff --git a/src-wallet/testdata/bob.pem b/src/wallet/testdata/bob.pem similarity index 100% rename from src-wallet/testdata/bob.pem rename to src/wallet/testdata/bob.pem diff --git a/src-wallet/testdata/carol.json b/src/wallet/testdata/carol.json similarity index 100% rename from src-wallet/testdata/carol.json rename to src/wallet/testdata/carol.json diff --git a/src-wallet/testdata/carol.pem b/src/wallet/testdata/carol.pem similarity index 100% rename from src-wallet/testdata/carol.pem rename to src/wallet/testdata/carol.pem diff --git a/src-wallet/testdata/withDummyMnemonic.json b/src/wallet/testdata/withDummyMnemonic.json similarity index 100% rename from src-wallet/testdata/withDummyMnemonic.json rename to src/wallet/testdata/withDummyMnemonic.json diff --git a/src-wallet/testdata/withDummySecretKey.json b/src/wallet/testdata/withDummySecretKey.json similarity index 100% rename from src-wallet/testdata/withDummySecretKey.json rename to src/wallet/testdata/withDummySecretKey.json diff --git a/src-wallet/testdata/withoutKind.json b/src/wallet/testdata/withoutKind.json similarity index 100% rename from src-wallet/testdata/withoutKind.json rename to src/wallet/testdata/withoutKind.json diff --git a/src-wallet/testutils/files.ts b/src/wallet/testutils/files.ts similarity index 100% rename from src-wallet/testutils/files.ts rename to src/wallet/testutils/files.ts diff --git a/src-wallet/testutils/message.ts b/src/wallet/testutils/message.ts similarity index 100% rename from src-wallet/testutils/message.ts rename to src/wallet/testutils/message.ts diff --git a/src-wallet/testutils/transaction.ts b/src/wallet/testutils/transaction.ts similarity index 100% rename from src-wallet/testutils/transaction.ts rename to src/wallet/testutils/transaction.ts diff --git a/src-wallet/testutils/wallets.ts b/src/wallet/testutils/wallets.ts similarity index 100% rename from src-wallet/testutils/wallets.ts rename to src/wallet/testutils/wallets.ts diff --git a/src-wallet/userAddress.ts b/src/wallet/userAddress.ts similarity index 100% rename from src-wallet/userAddress.ts rename to src/wallet/userAddress.ts diff --git a/src-wallet/userKeys.ts b/src/wallet/userKeys.ts similarity index 100% rename from src-wallet/userKeys.ts rename to src/wallet/userKeys.ts diff --git a/src-wallet/userSigner.ts b/src/wallet/userSigner.ts similarity index 100% rename from src-wallet/userSigner.ts rename to src/wallet/userSigner.ts diff --git a/src-wallet/userVerifier.ts b/src/wallet/userVerifier.ts similarity index 100% rename from src-wallet/userVerifier.ts rename to src/wallet/userVerifier.ts diff --git a/src-wallet/userWallet.ts b/src/wallet/userWallet.ts similarity index 100% rename from src-wallet/userWallet.ts rename to src/wallet/userWallet.ts diff --git a/src-wallet/users.spec.ts b/src/wallet/users.spec.ts similarity index 100% rename from src-wallet/users.spec.ts rename to src/wallet/users.spec.ts diff --git a/src-wallet/usersBenchmark.spec.ts b/src/wallet/usersBenchmark.spec.ts similarity index 100% rename from src-wallet/usersBenchmark.spec.ts rename to src/wallet/usersBenchmark.spec.ts diff --git a/src-wallet/validatorKeys.ts b/src/wallet/validatorKeys.ts similarity index 100% rename from src-wallet/validatorKeys.ts rename to src/wallet/validatorKeys.ts diff --git a/src-wallet/validatorSigner.ts b/src/wallet/validatorSigner.ts similarity index 100% rename from src-wallet/validatorSigner.ts rename to src/wallet/validatorSigner.ts diff --git a/src-wallet/validators.spec.ts b/src/wallet/validators.spec.ts similarity index 100% rename from src-wallet/validators.spec.ts rename to src/wallet/validators.spec.ts From 09713c3239c55990440631d706c702e8ff235762 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 8 Oct 2024 10:48:49 +0300 Subject: [PATCH 098/118] remove sdk-wallet dependency and fix import --- package-lock.json | 109 ------------------ package.json | 1 - src/index.ts | 1 + src/message.spec.ts | 2 +- src/testutils/wallets.ts | 2 +- src/transaction.spec.ts | 2 +- .../delegationTransactionsFactory.spec.ts | 2 +- src/wallet/validatorKeys.ts | 6 +- 8 files changed, 9 insertions(+), 116 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5aa4414f9..26f31f194 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,6 @@ "uuid": "8.3.2" }, "devDependencies": { - "@multiversx/sdk-wallet": "4.5.1", "@types/assert": "1.4.6", "@types/chai": "4.2.11", "@types/ed2curve": "0.2.2", @@ -53,12 +52,6 @@ "protobufjs": "^7.2.6" } }, - "@multiversx/sdk-wallet:4.0.0-beta.3": { - "extraneous": true - }, - "@multiversx/sdk-wallet@4.0.0-beta.3": { - "extraneous": true - }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -184,58 +177,6 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" }, - "node_modules/@multiversx/sdk-wallet": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-4.5.1.tgz", - "integrity": "sha512-rvaMUV6OxNj9gchOn7wSCPZcWc6hjs1nQuY6QwnaEcBfPsHtFemNNt+t3uxPZOrDhAwXqUgyy9WdltvOs8somg==", - "dev": true, - "dependencies": { - "@multiversx/sdk-bls-wasm": "0.3.5", - "@noble/ed25519": "1.7.3", - "@noble/hashes": "1.3.0", - "bech32": "1.1.4", - "bip39": "3.0.2", - "blake2b": "2.1.3", - "ed25519-hd-key": "1.1.2", - "ed2curve": "0.3.0", - "keccak": "3.0.1", - "scryptsy": "2.1.0", - "tweetnacl": "1.0.3", - "uuid": "8.3.2" - } - }, - "node_modules/@multiversx/sdk-wallet/node_modules/@types/node": { - "version": "11.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", - "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", - "dev": true - }, - "node_modules/@multiversx/sdk-wallet/node_modules/bip39": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", - "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", - "dev": true, - "dependencies": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - } - }, - "node_modules/@multiversx/sdk-wallet/node_modules/keccak": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", - "integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@noble/ed25519": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", @@ -5357,56 +5298,6 @@ } } }, - "@multiversx/sdk-wallet": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@multiversx/sdk-wallet/-/sdk-wallet-4.5.1.tgz", - "integrity": "sha512-rvaMUV6OxNj9gchOn7wSCPZcWc6hjs1nQuY6QwnaEcBfPsHtFemNNt+t3uxPZOrDhAwXqUgyy9WdltvOs8somg==", - "dev": true, - "requires": { - "@multiversx/sdk-bls-wasm": "0.3.5", - "@noble/ed25519": "1.7.3", - "@noble/hashes": "1.3.0", - "bech32": "1.1.4", - "bip39": "3.0.2", - "blake2b": "2.1.3", - "ed25519-hd-key": "1.1.2", - "ed2curve": "0.3.0", - "keccak": "3.0.1", - "scryptsy": "2.1.0", - "tweetnacl": "1.0.3", - "uuid": "8.3.2" - }, - "dependencies": { - "@types/node": { - "version": "11.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", - "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", - "dev": true - }, - "bip39": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", - "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", - "dev": true, - "requires": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - } - }, - "keccak": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz", - "integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==", - "dev": true, - "requires": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - } - } - }, "@noble/ed25519": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.3.tgz", diff --git a/package.json b/package.json index 061c1ab0a..4a5358470 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "uuid": "8.3.2" }, "devDependencies": { - "@multiversx/sdk-wallet": "4.5.1", "@types/assert": "1.4.6", "@types/chai": "4.2.11", "@types/ed2curve": "0.2.2", diff --git a/src/index.ts b/src/index.ts index 8ef8255b3..08c8affee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,3 +34,4 @@ export * from "./transactionsFactories"; export * from "./transactionsOutcomeParsers"; export * from "./utils"; export * from "./networkProviders"; +export * from "./wallet"; diff --git a/src/message.spec.ts b/src/message.spec.ts index b7c87c3c6..c7809d835 100644 --- a/src/message.spec.ts +++ b/src/message.spec.ts @@ -1,4 +1,4 @@ -import { UserVerifier } from "@multiversx/sdk-wallet"; +import { UserVerifier } from "./wallet"; import { assert } from "chai"; import { DEFAULT_MESSAGE_VERSION, SDK_JS_SIGNER, UNKNOWN_SIGNER } from "./constants"; import { Message, MessageComputer } from "./message"; diff --git a/src/testutils/wallets.ts b/src/testutils/wallets.ts index 1e67da63d..132d8c188 100644 --- a/src/testutils/wallets.ts +++ b/src/testutils/wallets.ts @@ -1,4 +1,4 @@ -import { UserSecretKey, UserSigner } from "@multiversx/sdk-wallet"; +import { UserSecretKey, UserSigner } from "./../wallet"; import axios from "axios"; import * as fs from "fs"; import * as path from "path"; diff --git a/src/transaction.spec.ts b/src/transaction.spec.ts index 6775fbb91..ecf98d5c0 100644 --- a/src/transaction.spec.ts +++ b/src/transaction.spec.ts @@ -1,4 +1,4 @@ -import { UserPublicKey, UserVerifier } from "@multiversx/sdk-wallet"; +import { UserPublicKey, UserVerifier } from "./wallet"; import BigNumber from "bignumber.js"; import { assert } from "chai"; import { Address } from "./address"; diff --git a/src/transactionsFactories/delegationTransactionsFactory.spec.ts b/src/transactionsFactories/delegationTransactionsFactory.spec.ts index 7d5591cc6..c7d72902f 100644 --- a/src/transactionsFactories/delegationTransactionsFactory.spec.ts +++ b/src/transactionsFactories/delegationTransactionsFactory.spec.ts @@ -1,4 +1,4 @@ -import { ValidatorPublicKey } from "@multiversx/sdk-wallet"; +import { ValidatorPublicKey } from "./../wallet"; import { assert } from "chai"; import { Address } from "../address"; import { DELEGATION_MANAGER_SC_ADDRESS } from "../constants"; diff --git a/src/wallet/validatorKeys.ts b/src/wallet/validatorKeys.ts index 3c3497622..7425e113d 100644 --- a/src/wallet/validatorKeys.ts +++ b/src/wallet/validatorKeys.ts @@ -2,7 +2,7 @@ import { guardLength } from "./assertions"; import { ErrInvariantFailed } from "./errors"; import { parseValidatorKey } from "./pem"; -const bls = require('@multiversx/sdk-bls-wasm'); +const bls = require("@multiversx/sdk-bls-wasm"); export const VALIDATOR_SECRETKEY_LENGTH = 32; export const VALIDATOR_PUBKEY_LENGTH = 96; @@ -22,7 +22,9 @@ export class BLS { static guardInitialized() { if (!BLS.isInitialized) { - throw new ErrInvariantFailed("BLS modules are not initalized. Make sure that 'await BLS.initIfNecessary()' is called correctly."); + throw new ErrInvariantFailed( + "BLS modules are not initalized. Make sure that 'await BLS.initIfNecessary()' is called correctly.", + ); } } } From 928092e32ca5f41177cc785ddc2e99234403b364 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 10 Oct 2024 11:55:02 +0300 Subject: [PATCH 099/118] Refactor testutils, remove duplicated files --- .vscode/settings.json | 5 +- .../testwallets/alice.json | 0 .../testwallets/alice.pem | 0 .../testwallets/bob.json | 0 .../testwallets/bob.pem | 0 .../testwallets/carol.json | 0 .../testwallets/carol.pem | 0 .../testwallets/dan.json | 0 .../testwallets/dan.pem | 0 .../testwallets/eve.json | 0 .../testwallets/eve.pem | 0 .../testwallets/frank.json | 0 .../testwallets/frank.pem | 0 .../testwallets/grace.json | 0 .../testwallets/grace.pem | 0 .../testwallets/heidi.json | 0 .../testwallets/heidi.pem | 0 .../testwallets/ivan.json | 0 .../testwallets/ivan.pem | 0 .../testwallets/judy.json | 0 .../testwallets/judy.pem | 0 .../testwallets/mallory.json | 0 .../testwallets/mallory.pem | 0 .../testwallets/mike.json | 0 .../testwallets/mike.pem | 0 .../testwallets/mnemonic.txt | 0 .../testwallets/password.txt | 0 .../testwallets}/withDummyMnemonic.json | 0 .../testwallets}/withDummySecretKey.json | 0 .../testwallets}/withoutKind.json | 0 src/{wallet => }/testutils/files.ts | 0 src/{wallet => }/testutils/message.ts | 0 src/{wallet => }/testutils/transaction.ts | 0 src/testutils/wallets.ts | 14 +- src/wallet/config.ts | 15 -- src/wallet/crypto/pubkeyEncrypt.spec.ts | 24 +- src/wallet/pem.spec.ts | 13 +- src/wallet/testdata/alice.json | 23 -- src/wallet/testdata/alice.pem | 5 - src/wallet/testdata/bob.json | 23 -- src/wallet/testdata/bob.pem | 5 - src/wallet/testdata/carol.json | 23 -- src/wallet/testdata/carol.pem | 5 - src/wallet/testutils/wallets.ts | 46 ---- src/wallet/userAddress.ts | 96 ------- src/wallet/userKeys.ts | 6 +- src/wallet/userSigner.ts | 8 +- src/wallet/users.spec.ts | 252 +++++++++++++----- 48 files changed, 230 insertions(+), 333 deletions(-) rename src/{testutils => testdata}/testwallets/alice.json (100%) rename src/{testutils => testdata}/testwallets/alice.pem (100%) rename src/{testutils => testdata}/testwallets/bob.json (100%) rename src/{testutils => testdata}/testwallets/bob.pem (100%) rename src/{testutils => testdata}/testwallets/carol.json (100%) rename src/{testutils => testdata}/testwallets/carol.pem (100%) rename src/{testutils => testdata}/testwallets/dan.json (100%) rename src/{testutils => testdata}/testwallets/dan.pem (100%) rename src/{testutils => testdata}/testwallets/eve.json (100%) rename src/{testutils => testdata}/testwallets/eve.pem (100%) rename src/{testutils => testdata}/testwallets/frank.json (100%) rename src/{testutils => testdata}/testwallets/frank.pem (100%) rename src/{testutils => testdata}/testwallets/grace.json (100%) rename src/{testutils => testdata}/testwallets/grace.pem (100%) rename src/{testutils => testdata}/testwallets/heidi.json (100%) rename src/{testutils => testdata}/testwallets/heidi.pem (100%) rename src/{testutils => testdata}/testwallets/ivan.json (100%) rename src/{testutils => testdata}/testwallets/ivan.pem (100%) rename src/{testutils => testdata}/testwallets/judy.json (100%) rename src/{testutils => testdata}/testwallets/judy.pem (100%) rename src/{testutils => testdata}/testwallets/mallory.json (100%) rename src/{testutils => testdata}/testwallets/mallory.pem (100%) rename src/{testutils => testdata}/testwallets/mike.json (100%) rename src/{testutils => testdata}/testwallets/mike.pem (100%) rename src/{testutils => testdata}/testwallets/mnemonic.txt (100%) rename src/{testutils => testdata}/testwallets/password.txt (100%) rename src/{wallet/testdata => testdata/testwallets}/withDummyMnemonic.json (100%) rename src/{wallet/testdata => testdata/testwallets}/withDummySecretKey.json (100%) rename src/{wallet/testdata => testdata/testwallets}/withoutKind.json (100%) rename src/{wallet => }/testutils/files.ts (100%) rename src/{wallet => }/testutils/message.ts (100%) rename src/{wallet => }/testutils/transaction.ts (100%) delete mode 100644 src/wallet/config.ts delete mode 100644 src/wallet/testdata/alice.json delete mode 100644 src/wallet/testdata/alice.pem delete mode 100644 src/wallet/testdata/bob.json delete mode 100644 src/wallet/testdata/bob.pem delete mode 100644 src/wallet/testdata/carol.json delete mode 100644 src/wallet/testdata/carol.pem delete mode 100644 src/wallet/testutils/wallets.ts delete mode 100644 src/wallet/userAddress.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index e5c7eee47..9a9afd464 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,8 @@ }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, } diff --git a/src/testutils/testwallets/alice.json b/src/testdata/testwallets/alice.json similarity index 100% rename from src/testutils/testwallets/alice.json rename to src/testdata/testwallets/alice.json diff --git a/src/testutils/testwallets/alice.pem b/src/testdata/testwallets/alice.pem similarity index 100% rename from src/testutils/testwallets/alice.pem rename to src/testdata/testwallets/alice.pem diff --git a/src/testutils/testwallets/bob.json b/src/testdata/testwallets/bob.json similarity index 100% rename from src/testutils/testwallets/bob.json rename to src/testdata/testwallets/bob.json diff --git a/src/testutils/testwallets/bob.pem b/src/testdata/testwallets/bob.pem similarity index 100% rename from src/testutils/testwallets/bob.pem rename to src/testdata/testwallets/bob.pem diff --git a/src/testutils/testwallets/carol.json b/src/testdata/testwallets/carol.json similarity index 100% rename from src/testutils/testwallets/carol.json rename to src/testdata/testwallets/carol.json diff --git a/src/testutils/testwallets/carol.pem b/src/testdata/testwallets/carol.pem similarity index 100% rename from src/testutils/testwallets/carol.pem rename to src/testdata/testwallets/carol.pem diff --git a/src/testutils/testwallets/dan.json b/src/testdata/testwallets/dan.json similarity index 100% rename from src/testutils/testwallets/dan.json rename to src/testdata/testwallets/dan.json diff --git a/src/testutils/testwallets/dan.pem b/src/testdata/testwallets/dan.pem similarity index 100% rename from src/testutils/testwallets/dan.pem rename to src/testdata/testwallets/dan.pem diff --git a/src/testutils/testwallets/eve.json b/src/testdata/testwallets/eve.json similarity index 100% rename from src/testutils/testwallets/eve.json rename to src/testdata/testwallets/eve.json diff --git a/src/testutils/testwallets/eve.pem b/src/testdata/testwallets/eve.pem similarity index 100% rename from src/testutils/testwallets/eve.pem rename to src/testdata/testwallets/eve.pem diff --git a/src/testutils/testwallets/frank.json b/src/testdata/testwallets/frank.json similarity index 100% rename from src/testutils/testwallets/frank.json rename to src/testdata/testwallets/frank.json diff --git a/src/testutils/testwallets/frank.pem b/src/testdata/testwallets/frank.pem similarity index 100% rename from src/testutils/testwallets/frank.pem rename to src/testdata/testwallets/frank.pem diff --git a/src/testutils/testwallets/grace.json b/src/testdata/testwallets/grace.json similarity index 100% rename from src/testutils/testwallets/grace.json rename to src/testdata/testwallets/grace.json diff --git a/src/testutils/testwallets/grace.pem b/src/testdata/testwallets/grace.pem similarity index 100% rename from src/testutils/testwallets/grace.pem rename to src/testdata/testwallets/grace.pem diff --git a/src/testutils/testwallets/heidi.json b/src/testdata/testwallets/heidi.json similarity index 100% rename from src/testutils/testwallets/heidi.json rename to src/testdata/testwallets/heidi.json diff --git a/src/testutils/testwallets/heidi.pem b/src/testdata/testwallets/heidi.pem similarity index 100% rename from src/testutils/testwallets/heidi.pem rename to src/testdata/testwallets/heidi.pem diff --git a/src/testutils/testwallets/ivan.json b/src/testdata/testwallets/ivan.json similarity index 100% rename from src/testutils/testwallets/ivan.json rename to src/testdata/testwallets/ivan.json diff --git a/src/testutils/testwallets/ivan.pem b/src/testdata/testwallets/ivan.pem similarity index 100% rename from src/testutils/testwallets/ivan.pem rename to src/testdata/testwallets/ivan.pem diff --git a/src/testutils/testwallets/judy.json b/src/testdata/testwallets/judy.json similarity index 100% rename from src/testutils/testwallets/judy.json rename to src/testdata/testwallets/judy.json diff --git a/src/testutils/testwallets/judy.pem b/src/testdata/testwallets/judy.pem similarity index 100% rename from src/testutils/testwallets/judy.pem rename to src/testdata/testwallets/judy.pem diff --git a/src/testutils/testwallets/mallory.json b/src/testdata/testwallets/mallory.json similarity index 100% rename from src/testutils/testwallets/mallory.json rename to src/testdata/testwallets/mallory.json diff --git a/src/testutils/testwallets/mallory.pem b/src/testdata/testwallets/mallory.pem similarity index 100% rename from src/testutils/testwallets/mallory.pem rename to src/testdata/testwallets/mallory.pem diff --git a/src/testutils/testwallets/mike.json b/src/testdata/testwallets/mike.json similarity index 100% rename from src/testutils/testwallets/mike.json rename to src/testdata/testwallets/mike.json diff --git a/src/testutils/testwallets/mike.pem b/src/testdata/testwallets/mike.pem similarity index 100% rename from src/testutils/testwallets/mike.pem rename to src/testdata/testwallets/mike.pem diff --git a/src/testutils/testwallets/mnemonic.txt b/src/testdata/testwallets/mnemonic.txt similarity index 100% rename from src/testutils/testwallets/mnemonic.txt rename to src/testdata/testwallets/mnemonic.txt diff --git a/src/testutils/testwallets/password.txt b/src/testdata/testwallets/password.txt similarity index 100% rename from src/testutils/testwallets/password.txt rename to src/testdata/testwallets/password.txt diff --git a/src/wallet/testdata/withDummyMnemonic.json b/src/testdata/testwallets/withDummyMnemonic.json similarity index 100% rename from src/wallet/testdata/withDummyMnemonic.json rename to src/testdata/testwallets/withDummyMnemonic.json diff --git a/src/wallet/testdata/withDummySecretKey.json b/src/testdata/testwallets/withDummySecretKey.json similarity index 100% rename from src/wallet/testdata/withDummySecretKey.json rename to src/testdata/testwallets/withDummySecretKey.json diff --git a/src/wallet/testdata/withoutKind.json b/src/testdata/testwallets/withoutKind.json similarity index 100% rename from src/wallet/testdata/withoutKind.json rename to src/testdata/testwallets/withoutKind.json diff --git a/src/wallet/testutils/files.ts b/src/testutils/files.ts similarity index 100% rename from src/wallet/testutils/files.ts rename to src/testutils/files.ts diff --git a/src/wallet/testutils/message.ts b/src/testutils/message.ts similarity index 100% rename from src/wallet/testutils/message.ts rename to src/testutils/message.ts diff --git a/src/wallet/testutils/transaction.ts b/src/testutils/transaction.ts similarity index 100% rename from src/wallet/testutils/transaction.ts rename to src/testutils/transaction.ts diff --git a/src/testutils/wallets.ts b/src/testutils/wallets.ts index 132d8c188..9c5add288 100644 --- a/src/testutils/wallets.ts +++ b/src/testutils/wallets.ts @@ -1,4 +1,3 @@ -import { UserSecretKey, UserSigner } from "./../wallet"; import axios from "axios"; import * as fs from "fs"; import * as path from "path"; @@ -6,8 +5,12 @@ import { Account } from "../account"; import { Address } from "../address"; import { IAddress } from "../interface"; import { IAccountOnNetwork } from "../interfaceOfNetwork"; +import { UserSecretKey, UserSigner } from "./../wallet"; +import { readTestFile } from "./files"; import { isOnBrowserTests } from "./utils"; +export const DummyMnemonicOf12Words = "matter trumpet twenty parade fame north lift sail valve salon foster cinnamon"; + interface IAccountFetcher { getAccount(address: IAddress): Promise; } @@ -45,6 +48,13 @@ export async function loadTestWallets(): Promise> { return walletMap; } +export async function loadTestKeystore(file: string): Promise { + const testdataPath = path.resolve(__dirname, "..", "testdata/testwallets"); + const keystorePath = path.resolve(testdataPath, file); + const json = await readTestFile(keystorePath); + return JSON.parse(json); +} + export async function loadMnemonic(): Promise { return await readTestWalletFileContents("mnemonic.txt"); } @@ -62,7 +72,7 @@ export async function loadTestWallet(name: string): Promise { } async function readTestWalletFileContents(name: string): Promise { - let filePath = path.join("src", "testutils", "testwallets", name); + let filePath = path.join("src", "testdata", "testwallets", name); if (isOnBrowserTests()) { return await downloadTextFile(filePath); diff --git a/src/wallet/config.ts b/src/wallet/config.ts deleted file mode 100644 index cf60ce5a4..000000000 --- a/src/wallet/config.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * 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"; -} diff --git a/src/wallet/crypto/pubkeyEncrypt.spec.ts b/src/wallet/crypto/pubkeyEncrypt.spec.ts index 291ca391e..804a35f5b 100644 --- a/src/wallet/crypto/pubkeyEncrypt.spec.ts +++ b/src/wallet/crypto/pubkeyEncrypt.spec.ts @@ -1,8 +1,8 @@ import { assert } from "chai"; -import { loadTestWallet, TestWallet } from "../testutils/wallets"; -import { PubkeyEncryptor } from "./pubkeyEncryptor"; +import { loadTestWallet, TestWallet } from "../../testutils/wallets"; import { UserPublicKey, UserSecretKey } from "../userKeys"; import { PubkeyDecryptor } from "./pubkeyDecryptor"; +import { PubkeyEncryptor } from "./pubkeyEncryptor"; import { X25519EncryptedData } from "./x25519EncryptedData"; describe("test address", () => { @@ -15,21 +15,31 @@ describe("test address", () => { bob = await loadTestWallet("bob"); carol = await loadTestWallet("carol"); - encryptedDataOfAliceForBob = PubkeyEncryptor.encrypt(sensitiveData, new UserPublicKey(bob.address.pubkey()), new UserSecretKey(alice.secretKey)); + encryptedDataOfAliceForBob = PubkeyEncryptor.encrypt( + sensitiveData, + new UserPublicKey(bob.address.pubkey()), + new UserSecretKey(alice.secretKey), + ); }); - it("encrypts/decrypts", () => { + it("encrypts/decrypts", () => { const decryptedData = PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey)); - assert.equal(sensitiveData.toString('hex'), decryptedData.toString('hex')); + 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"); + 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"); + assert.throws( + () => PubkeyDecryptor.decrypt(encryptedDataOfAliceForBob, new UserSecretKey(bob.secretKey)), + "Invalid authentication for encrypted message originator", + ); }); }); diff --git a/src/wallet/pem.spec.ts b/src/wallet/pem.spec.ts index 4861b230b..538a99cfc 100644 --- a/src/wallet/pem.spec.ts +++ b/src/wallet/pem.spec.ts @@ -1,9 +1,9 @@ +import { Buffer } from "buffer"; import { assert } from "chai"; +import { loadTestWallet, TestWallet } from "./../testutils/wallets"; +import { ErrBadPEM } from "./errors"; import { parse, parseUserKey, parseValidatorKey } from "./pem"; -import { Buffer } from "buffer"; import { BLS } from "./validatorKeys"; -import { ErrBadPEM } from "./errors"; -import { loadTestWallet, TestWallet } from "./testutils/wallets"; describe("test PEMs", () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; @@ -32,7 +32,10 @@ MWU0YzE3YTRjZDc3NDI0Nw== let validatorKey = parseValidatorKey(pem); assert.equal(validatorKey.hex(), "7cff99bd671502db7d15bc8abc0c9a804fb925406fbdd50f1e4c17a4cd774247"); - assert.equal(validatorKey.generatePublicKey().hex(), "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208"); + assert.equal( + validatorKey.generatePublicKey().hex(), + "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); }); it("should parse multi-key PEM files", () => { @@ -44,7 +47,7 @@ MWU0YzE3YTRjZDc3NDI0Nw== let expected = [ Buffer.concat([alice.secretKey, alice.address.pubkey()]), Buffer.concat([bob.secretKey, bob.address.pubkey()]), - Buffer.concat([carol.secretKey, carol.address.pubkey()]) + Buffer.concat([carol.secretKey, carol.address.pubkey()]), ]; let trivialContent = `-----BEGIN PRIVATE KEY for alice diff --git a/src/wallet/testdata/alice.json b/src/wallet/testdata/alice.json deleted file mode 100644 index 18c4ea1e9..000000000 --- a/src/wallet/testdata/alice.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 4, - "kind": "secretKey", - "id": "0dc10c02-b59b-4bac-9710-6b2cfa4284ba", - "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", - "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", - "crypto": { - "ciphertext": "4c41ef6fdfd52c39b1585a875eb3c86d30a315642d0e35bb8205b6372c1882f135441099b11ff76345a6f3a930b5665aaf9f7325a32c8ccd60081c797aa2d538", - "cipherparams": { - "iv": "033182afaa1ebaafcde9ccc68a5eac31" - }, - "cipher": "aes-128-ctr", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "salt": "4903bd0e7880baa04fc4f886518ac5c672cdc745a6bd13dcec2b6c12e9bffe8d", - "n": 4096, - "r": 8, - "p": 1 - }, - "mac": "5b4a6f14ab74ba7ca23db6847e28447f0e6a7724ba9664cf425df707a84f5a8b" - } -} diff --git a/src/wallet/testdata/alice.pem b/src/wallet/testdata/alice.pem deleted file mode 100644 index d27bb68b4..000000000 --- a/src/wallet/testdata/alice.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- -NDEzZjQyNTc1ZjdmMjZmYWQzMzE3YTc3ODc3MTIxMmZkYjgwMjQ1ODUwOTgxZTQ4 -YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1ZDQy -MWYyNGMyOTE4MWU2Mzg4ODIyOGRjODFjYTYwZDY5ZTE= ------END PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- \ No newline at end of file diff --git a/src/wallet/testdata/bob.json b/src/wallet/testdata/bob.json deleted file mode 100644 index 9efb41109..000000000 --- a/src/wallet/testdata/bob.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 4, - "kind": "secretKey", - "id": "85fdc8a7-7119-479d-b7fb-ab4413ed038d", - "address": "8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", - "bech32": "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", - "crypto": { - "ciphertext": "c2664a31350aaf6a00525560db75c254d0aea65dc466441356c1dd59253cceb9e83eb05730ef3f42a11573c9a0e33dd952d488f00535b35357bb41d127b1eb82", - "cipherparams": { - "iv": "18378411e31f6c4e99f1435d9ab82831" - }, - "cipher": "aes-128-ctr", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "salt": "18304455ac2dbe2a2018bda162bd03ef95b81622e99d8275c34a6d5e6932a68b", - "n": 4096, - "r": 8, - "p": 1 - }, - "mac": "23756172195ac483fa29025dc331bc7aa2c139533922a8dc08642eb0a677541f" - } -} diff --git a/src/wallet/testdata/bob.pem b/src/wallet/testdata/bob.pem deleted file mode 100644 index 00b5bc4ef..000000000 --- a/src/wallet/testdata/bob.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY for erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx----- -YjhjYTZmODIwM2ZiNGI1NDVhOGU4M2M1Mzg0ZGEwMzNjNDE1ZGIxNTViNTNmYjVi -OGViYTdmZjVhMDM5ZDYzOTgwNDlkNjM5ZTVhNjk4MGQxY2QyMzkyYWJjY2U0MTAy -OWNkYTc0YTE1NjM1MjNhMjAyZjA5NjQxY2MyNjE4Zjg= ------END PRIVATE KEY for erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx----- \ No newline at end of file diff --git a/src/wallet/testdata/carol.json b/src/wallet/testdata/carol.json deleted file mode 100644 index 1014a8230..000000000 --- a/src/wallet/testdata/carol.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 4, - "kind": "secretKey", - "id": "65894f35-d142-41d2-9335-6ad02e0ed0be", - "address": "b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", - "bech32": "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", - "crypto": { - "ciphertext": "bdfb984a1e7c7460f0a289749609730cdc99d7ce85b59305417c2c0f007b2a6aaa7203dd94dbf27315bced39b0b281769fbc70b01e6e57f89ae2f2a9e9100007", - "cipherparams": { - "iv": "258ed2b4dc506b4dc9d274b0449b0eb0" - }, - "cipher": "aes-128-ctr", - "kdf": "scrypt", - "kdfparams": { - "dklen": 32, - "salt": "4f2f5530ce28dc0210962589b908f52714f75c8fb79ff18bdd0024c43c7a220b", - "n": 4096, - "r": 8, - "p": 1 - }, - "mac": "f8de52e2627024eaa33f2ee5eadcd3d3815e10dd274ea966dc083d000cc8b258" - } -} diff --git a/src/wallet/testdata/carol.pem b/src/wallet/testdata/carol.pem deleted file mode 100644 index 5551c9c0e..000000000 --- a/src/wallet/testdata/carol.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY for erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8----- -ZTI1M2E1NzFjYTE1M2RjMmFlZTg0NTgxOWY3NGJjYzk3NzNiMDU4NmVkZWFkMTVh -OTRjYjcyMzVhNTAyNzQzNmIyYTExNTU1Y2U1MjFlNDk0NGUwOWFiMTc1NDlkODVi -NDg3ZGNkMjZjODRiNTAxN2EzOWUzMWEzNjcwODg5YmE= ------END PRIVATE KEY for erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8----- \ No newline at end of file diff --git a/src/wallet/testutils/wallets.ts b/src/wallet/testutils/wallets.ts deleted file mode 100644 index 1ce1af3e5..000000000 --- a/src/wallet/testutils/wallets.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as path from "path"; -import { UserAddress } from "../userAddress"; -import { UserSecretKey } from "../userKeys"; -import { readTestFile } from "./files"; - -export const DummyPassword = "password"; -export const DummyMnemonic = "moral volcano peasant pass circle pen over picture flat shop clap goat never lyrics gather prepare woman film husband gravity behind test tiger improve"; -export const DummyMnemonicOf12Words = "matter trumpet twenty parade fame north lift sail valve salon foster cinnamon"; - -export async function loadTestWallet(name: string): Promise { - const keystore = await loadTestKeystore(`${name}.json`) - const pemText = await loadTestPemFile(`${name}.pem`) - const pemKey = UserSecretKey.fromPem(pemText); - const address = new UserAddress(Buffer.from(keystore.address, "hex")); - - return new TestWallet(address, pemKey.hex(), keystore, pemText); -} - -export async function loadTestKeystore(file: string): Promise { - const testdataPath = path.resolve(__dirname, "..", "testdata"); - const keystorePath = path.resolve(testdataPath, file); - const json = await readTestFile(keystorePath); - return JSON.parse(json); -} - -export async function loadTestPemFile(file: string): Promise { - const testdataPath = path.resolve(__dirname, "..", "testdata"); - const pemFilePath = path.resolve(testdataPath, file); - return await readTestFile(pemFilePath); -} - -export class TestWallet { - readonly address: UserAddress; - readonly secretKeyHex: string; - readonly secretKey: Buffer; - readonly keyFileObject: any; - readonly pemFileText: any; - - constructor(address: UserAddress, secretKeyHex: string, keyFileObject: any, pemFileText: any) { - this.address = address; - this.secretKeyHex = secretKeyHex; - this.secretKey = Buffer.from(secretKeyHex, "hex"); - this.keyFileObject = keyFileObject; - this.pemFileText = pemFileText; - } -} diff --git a/src/wallet/userAddress.ts b/src/wallet/userAddress.ts deleted file mode 100644 index b7a885cf8..000000000 --- a/src/wallet/userAddress.ts +++ /dev/null @@ -1,96 +0,0 @@ -import * as bech32 from "bech32"; -import { LibraryConfig } from "./config"; -import { ErrBadAddress } from "./errors"; - -/** - * @internal - * For internal use only. - */ -export class UserAddress { - private readonly buffer: Buffer; - private readonly hrp: string; - - public constructor(buffer: Buffer, hrp?: string) { - this.buffer = buffer; - this.hrp = hrp || LibraryConfig.DefaultAddressHrp; - } - - static newFromBech32(value: string): UserAddress { - const { hrp, pubkey } = decodeFromBech32({ value, allowCustomHrp: true }); - return new UserAddress(pubkey, hrp); - } - - /** - * @internal - * @deprecated - */ - static fromBech32(value: string): UserAddress { - // On this legacy flow, we do not accept addresses with custom hrp (in order to avoid behavioral breaking changes). - const { hrp, pubkey } = decodeFromBech32({ value, allowCustomHrp: false }); - return new UserAddress(pubkey, hrp); - } - - /** - * Returns the hex representation of the address (pubkey) - */ - hex(): string { - return this.buffer.toString("hex"); - } - - /** - * Returns the bech32 representation of the address - */ - bech32(): string { - const words = bech32.toWords(this.pubkey()); - const address = bech32.encode(this.hrp, words); - return address; - } - - /** - * Returns the pubkey as raw bytes (buffer) - */ - pubkey(): Buffer { - return this.buffer; - } - - /** - * Returns the bech32 representation of the address - */ - toString(): string { - return this.bech32(); - } - - /** - * Converts the address to a pretty, plain JavaScript object. - */ - toJSON(): object { - return { - bech32: this.bech32(), - pubkey: this.hex() - }; - } -} - -function decodeFromBech32(options: { value: string; allowCustomHrp: boolean }): { hrp: string; pubkey: Buffer } { - const value = options.value; - const allowCustomHrp = options.allowCustomHrp; - - let hrp: string; - let pubkey: Buffer; - - try { - const decoded = bech32.decode(value); - - hrp = decoded.prefix; - pubkey = Buffer.from(bech32.fromWords(decoded.words)); - } catch (err: any) { - throw new ErrBadAddress(value, err); - } - - // Workaround, in order to avoid behavioral breaking changes on legacy flows. - if (!allowCustomHrp && hrp != LibraryConfig.DefaultAddressHrp) { - throw new ErrBadAddress(value); - } - - return { hrp, pubkey }; -} diff --git a/src/wallet/userKeys.ts b/src/wallet/userKeys.ts index 6acb76f00..eaee0e10a 100644 --- a/src/wallet/userKeys.ts +++ b/src/wallet/userKeys.ts @@ -1,8 +1,8 @@ import * as ed from "@noble/ed25519"; import { sha512 } from "@noble/hashes/sha512"; +import { Address } from "../address"; import { guardLength } from "./assertions"; import { parseUserKey } from "./pem"; -import { UserAddress } from "./userAddress"; export const USER_SEED_LENGTH = 32; export const USER_PUBKEY_LENGTH = 32; @@ -73,8 +73,8 @@ export class UserPublicKey { return this.buffer.toString("hex"); } - toAddress(hrp?: string): UserAddress { - return new UserAddress(this.buffer, hrp); + toAddress(hrp?: string): Address { + return new Address(this.buffer, hrp); } valueOf(): Buffer { diff --git a/src/wallet/userSigner.ts b/src/wallet/userSigner.ts index b78d5048d..37f05e302 100644 --- a/src/wallet/userSigner.ts +++ b/src/wallet/userSigner.ts @@ -1,5 +1,5 @@ +import { Address } from "../address"; import { ErrSignerCannotSign } from "./errors"; -import { UserAddress } from "./userAddress"; import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; @@ -9,7 +9,7 @@ interface IUserSecretKey { } interface IUserPublicKey { - toAddress(hrp?: string): { bech32(): string; }; + toAddress(hrp?: string): { bech32(): string }; } /** @@ -44,8 +44,8 @@ export class UserSigner { /** * Gets the address of the signer. */ - getAddress(hrp?: string): UserAddress { + getAddress(hrp?: string): Address { const bech32 = this.secretKey.generatePublicKey().toAddress(hrp).bech32(); - return UserAddress.newFromBech32(bech32); + return Address.newFromBech32(bech32); } } diff --git a/src/wallet/users.spec.ts b/src/wallet/users.spec.ts index dc6d8d4fc..815c0000e 100644 --- a/src/wallet/users.spec.ts +++ b/src/wallet/users.spec.ts @@ -1,25 +1,26 @@ import { assert } from "chai"; -import { Randomness } from "./crypto"; -import { ErrBadMnemonicEntropy, ErrInvariantFailed } from "./errors"; -import { Mnemonic } from "./mnemonic"; -import { TestMessage } from "./testutils/message"; -import { TestTransaction } from "./testutils/transaction"; +import { TestMessage } from "./../testutils/message"; +import { TestTransaction } from "./../testutils/transaction"; import { - DummyMnemonic, DummyMnemonicOf12Words, - DummyPassword, + loadMnemonic, + loadPassword, loadTestKeystore, loadTestWallet, TestWallet, -} from "./testutils/wallets"; +} from "./../testutils/wallets"; +import { Randomness } from "./crypto"; +import { ErrBadMnemonicEntropy, ErrInvariantFailed } from "./errors"; +import { Mnemonic } from "./mnemonic"; import { UserSecretKey } from "./userKeys"; import { UserSigner } from "./userSigner"; import { UserVerifier } from "./userVerifier"; import { UserWallet } from "./userWallet"; -describe("test user wallets", () => { +describe("test user wallets", async () => { let alice: TestWallet, bob: TestWallet, carol: TestWallet; - let password: string = DummyPassword; + let password: string = await loadPassword(); + const dummyMnemonic = await loadMnemonic(); before(async function () { alice = await loadTestWallet("alice"); @@ -62,7 +63,7 @@ describe("test user wallets", () => { }); it("should derive keys", async () => { - let mnemonic = Mnemonic.fromString(DummyMnemonic); + let mnemonic = Mnemonic.fromString(dummyMnemonic); assert.equal(mnemonic.deriveKey(0).hex(), alice.secretKeyHex); assert.equal(mnemonic.deriveKey(1).hex(), bob.secretKeyHex); @@ -72,13 +73,31 @@ describe("test user wallets", () => { it("should derive keys (12 words)", async () => { const mnemonic = Mnemonic.fromString(DummyMnemonicOf12Words); - assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), "erd1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqg4g7na"); - assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), "erd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqnts4zs09p"); - assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), "erd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksptewtj"); + assert.equal( + mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), + "erd1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqg4g7na", + ); + assert.equal( + mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), + "erd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqnts4zs09p", + ); + assert.equal( + mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), + "erd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksptewtj", + ); - assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress("test").bech32(), "test1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqc6tnnf"); - assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress("xerd").bech32(), "xerd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqntsj4adj4"); - assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress("yerd").bech32(), "yerd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksn8p0n5"); + assert.equal( + mnemonic.deriveKey(0).generatePublicKey().toAddress("test").bech32(), + "test1l8g9dk3gz035gkjhwegsjkqzdu3augrwhcfxrnucnyyrpc2220pqc6tnnf", + ); + assert.equal( + mnemonic.deriveKey(1).generatePublicKey().toAddress("xerd").bech32(), + "xerd1fmhwg84rldg0xzngf53m0y607wvefvamh07n2mkypedx27lcqntsj4adj4", + ); + assert.equal( + mnemonic.deriveKey(2).generatePublicKey().toAddress("yerd").bech32(), + "yerd1tyuyemt4xz2yjvc7rxxp8kyfmk2n3h8gv3aavzd9ru4v2vhrkcksn8p0n5", + ); }); it("should create secret key", () => { @@ -150,8 +169,8 @@ describe("test user wallets", () => { randomness: new Randomness({ id: alice.keyFileObject.id, iv: Buffer.from(alice.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(alice.keyFileObject.crypto.kdfparams.salt, "hex") - }) + salt: Buffer.from(alice.keyFileObject.crypto.kdfparams.salt, "hex"), + }), }); bobKeyFile = UserWallet.fromSecretKey({ @@ -160,8 +179,8 @@ describe("test user wallets", () => { randomness: new Randomness({ id: bob.keyFileObject.id, iv: Buffer.from(bob.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(bob.keyFileObject.crypto.kdfparams.salt, "hex") - }) + salt: Buffer.from(bob.keyFileObject.crypto.kdfparams.salt, "hex"), + }), }); carolKeyFile = UserWallet.fromSecretKey({ @@ -170,8 +189,8 @@ describe("test user wallets", () => { randomness: new Randomness({ id: carol.keyFileObject.id, iv: Buffer.from(carol.keyFileObject.crypto.cipherparams.iv, "hex"), - salt: Buffer.from(carol.keyFileObject.crypto.kdfparams.salt, "hex") - }) + salt: Buffer.from(carol.keyFileObject.crypto.kdfparams.salt, "hex"), + }), }); assert.deepEqual(aliceKeyFile.toJSON(), alice.keyFileObject); @@ -183,13 +202,16 @@ describe("test user wallets", () => { const keyFileObject = await loadTestKeystore("withoutKind.json"); const secretKey = UserWallet.decryptSecretKey(keyFileObject, password); - assert.equal(secretKey.generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal( + secretKey.generatePublicKey().toAddress().bech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); }); it("should create and load keystore files (with mnemonics)", async function () { this.timeout(10000); - const wallet = UserWallet.fromMnemonic({ mnemonic: DummyMnemonic, password: password }); + const wallet = UserWallet.fromMnemonic({ mnemonic: dummyMnemonic, password: password }); const json = wallet.toJSON(); assert.equal(json.version, 4); @@ -199,7 +221,7 @@ describe("test user wallets", () => { const mnemonic = UserWallet.decryptMnemonic(json, password); const mnemonicText = mnemonic.toString(); - assert.equal(mnemonicText, DummyMnemonic); + assert.equal(mnemonicText, dummyMnemonic); assert.equal(mnemonic.deriveKey(0).generatePublicKey().toAddress().bech32(), alice.address.bech32()); assert.equal(mnemonic.deriveKey(1).generatePublicKey().toAddress().bech32(), bob.address.bech32()); assert.equal(mnemonic.deriveKey(2).generatePublicKey().toAddress().bech32(), carol.address.bech32()); @@ -207,13 +229,13 @@ describe("test user wallets", () => { // With provided randomness, in order to reproduce our test wallets const expectedDummyWallet = await loadTestKeystore("withDummyMnemonic.json"); const dummyWallet = UserWallet.fromMnemonic({ - mnemonic: DummyMnemonic, + mnemonic: dummyMnemonic, password: password, randomness: new Randomness({ id: "5b448dbc-5c72-4d83-8038-938b1f8dff19", iv: Buffer.from("2da5620906634972d9a623bc249d63d4", "hex"), - salt: Buffer.from("aa9e0ba6b188703071a582c10e5331f2756279feb0e2768f1ba0fd38ec77f035", "hex") - }) + salt: Buffer.from("aa9e0ba6b188703071a582c10e5331f2756279feb0e2768f1ba0fd38ec77f035", "hex"), + }), }); assert.deepEqual(dummyWallet.toJSON(), expectedDummyWallet); @@ -223,26 +245,47 @@ describe("test user wallets", () => { const keyFileObject = await loadTestKeystore("withoutKind.json"); const secretKey = UserWallet.decrypt(keyFileObject, password); - assert.equal(secretKey.generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.equal( + secretKey.generatePublicKey().toAddress().bech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); }); it("should throw when calling loadSecretKey with unecessary address index", async function () { const keyFileObject = await loadTestKeystore("alice.json"); - assert.throws(() => UserWallet.decrypt(keyFileObject, password, 42), "addressIndex must not be provided when kind == 'secretKey'"); + assert.throws( + () => UserWallet.decrypt(keyFileObject, password, 42), + "addressIndex must not be provided when kind == 'secretKey'", + ); }); it("should loadSecretKey with mnemonic", async function () { const keyFileObject = await loadTestKeystore("withDummyMnemonic.json"); - assert.equal(UserWallet.decrypt(keyFileObject, password, 0).generatePublicKey().toAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.equal(UserWallet.decrypt(keyFileObject, password, 1).generatePublicKey().toAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - assert.equal(UserWallet.decrypt(keyFileObject, password, 2).generatePublicKey().toAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + assert.equal( + UserWallet.decrypt(keyFileObject, password, 0).generatePublicKey().toAddress().bech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.equal( + UserWallet.decrypt(keyFileObject, password, 1).generatePublicKey().toAddress().bech32(), + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + ); + assert.equal( + UserWallet.decrypt(keyFileObject, password, 2).generatePublicKey().toAddress().bech32(), + "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + ); }); it("should sign transactions", async () => { - let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); - let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); + let signer = new UserSigner( + UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"), + ); + let verifier = new UserVerifier( + UserSecretKey.fromString( + "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf", + ).generatePublicKey(), + ); // With data field let transaction = new TestTransaction({ @@ -259,10 +302,16 @@ describe("test user wallets", () => { let signature = await signer.sign(serialized); assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized))); - assert.equal(serialized.toString(), `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`); - assert.equal(signature.toString("hex"), "a3b61a2fe461f3393c42e6cb0477a6b52ffd92168f10c111f6aa8d0a310ee0c314fae0670f8313f1ad992933ac637c61a8ff20cc20b6a8b2260a4af1a120a70d"); + assert.equal( + serialized.toString(), + `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","version":1}`, + ); + assert.equal( + signature.toString("hex"), + "a3b61a2fe461f3393c42e6cb0477a6b52ffd92168f10c111f6aa8d0a310ee0c314fae0670f8313f1ad992933ac637c61a8ff20cc20b6a8b2260a4af1a120a70d", + ); assert.isTrue(verifier.verify(serialized, signature)); - + // Without data field transaction = new TestTransaction({ nonce: 8, @@ -270,21 +319,33 @@ describe("test user wallets", () => { receiver: "erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r", gasPrice: 1000000000, gasLimit: 50000, - chainID: "1" + chainID: "1", }); serialized = transaction.serializeForSigning(); signature = await signer.sign(serialized); assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized))); - assert.equal(serialized.toString(), `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`); - assert.equal(signature.toString("hex"), "f136c901d37349a7da8cfe3ab5ec8ef333b0bc351517c0e9bef9eb9704aed3077bf222769cade5ff29dffe5f42e4f0c5e0b068bdba90cd2cb41da51fd45d5a03"); + assert.equal( + serialized.toString(), + `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","version":1}`, + ); + assert.equal( + signature.toString("hex"), + "f136c901d37349a7da8cfe3ab5ec8ef333b0bc351517c0e9bef9eb9704aed3077bf222769cade5ff29dffe5f42e4f0c5e0b068bdba90cd2cb41da51fd45d5a03", + ); }); it("guardian should sign transactions from PEM", async () => { // bob is the guardian - let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); - let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); + let signer = new UserSigner( + UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"), + ); + let verifier = new UserVerifier( + UserSecretKey.fromString( + "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf", + ).generatePublicKey(), + ); let guardianSigner = new UserSigner(UserSecretKey.fromPem(bob.pemFileText)); // With data field @@ -299,16 +360,25 @@ describe("test user wallets", () => { chainID: "1", guardian: "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", options: 2, - version: 2 + version: 2, }); let serialized = transaction.serializeForSigning(); let signature = await signer.sign(serialized); let guardianSignature = await guardianSigner.sign(serialized); - assert.equal(serialized.toString(), `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","options":2,"version":2}`); - assert.equal(signature.toString("hex"), "00b867ae749616954711ef227c0a3f5c6556246f26dbde12ad929a099094065341a0fae7c5ced98e6bdd100ce922c975667444ea859dce9597b46e63cade2a03"); - assert.equal(guardianSignature.toString("hex"), "1326e44941ef7bfbad3edf346e72abe23704ee32b4b6a6a6a9b793bd7c62b6d4a69d3c6ea2dddf7eabc8df8fe291cd24822409ab9194b6a0f3bbbf1c59b0a10f"); + assert.equal( + serialized.toString(), + `{"nonce":0,"value":"0","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","gasPrice":1000000000,"gasLimit":50000,"data":"Zm9v","chainID":"1","options":2,"version":2}`, + ); + assert.equal( + signature.toString("hex"), + "00b867ae749616954711ef227c0a3f5c6556246f26dbde12ad929a099094065341a0fae7c5ced98e6bdd100ce922c975667444ea859dce9597b46e63cade2a03", + ); + assert.equal( + guardianSignature.toString("hex"), + "1326e44941ef7bfbad3edf346e72abe23704ee32b4b6a6a6a9b793bd7c62b6d4a69d3c6ea2dddf7eabc8df8fe291cd24822409ab9194b6a0f3bbbf1c59b0a10f", + ); assert.isTrue(verifier.verify(serialized, signature)); // Without data field @@ -329,9 +399,18 @@ describe("test user wallets", () => { signature = await signer.sign(serialized); guardianSignature = await guardianSigner.sign(serialized); - assert.equal(serialized.toString(), `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","options":2,"version":2}`); - assert.equal(signature.toString("hex"), "49a63fa0e3cfb81a2b6d926c741328fb270ea4f58fa32585fe8aa3cde191245e5a13c5c059d5576f4c05fc24d2534a2124ff79c98d067ce8412c806779066b03"); - assert.equal(guardianSignature.toString("hex"), "4c25a54381bf66576d05f32659d30672b5b0bfbfb6b6aee52290d28cfbc87860637f095f83663a1893d12d0d5a27b2ab3325829ff1f1215b81a7ced8ee5d7203"); + assert.equal( + serialized.toString(), + `{"nonce":8,"value":"10000000000000000000","receiver":"erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r","sender":"erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz","guardian":"erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx","gasPrice":1000000000,"gasLimit":50000,"chainID":"1","options":2,"version":2}`, + ); + assert.equal( + signature.toString("hex"), + "49a63fa0e3cfb81a2b6d926c741328fb270ea4f58fa32585fe8aa3cde191245e5a13c5c059d5576f4c05fc24d2534a2124ff79c98d067ce8412c806779066b03", + ); + assert.equal( + guardianSignature.toString("hex"), + "4c25a54381bf66576d05f32659d30672b5b0bfbfb6b6aee52290d28cfbc87860637f095f83663a1893d12d0d5a27b2ab3325829ff1f1215b81a7ced8ee5d7203", + ); assert.isTrue(verifier.verify(serialized, signature)); }); @@ -345,23 +424,32 @@ describe("test user wallets", () => { gasPrice: 1000000000, gasLimit: 50000, data: "foo", - chainID: "1" + chainID: "1", }); const serialized = transaction.serializeForSigning(); const signature = await signer.sign(serialized); assert.deepEqual(await signer.sign(serialized), await signer.sign(Uint8Array.from(serialized))); - assert.equal(signature.toString("hex"), "ba4fa95fea1402e4876abf1d5a510615aab374ee48bb76f5230798a7d3f2fcae6ba91ba56c6d62e6e7003ce531ff02f219cb7218dd00dd2ca650ba747f19640a"); + assert.equal( + signature.toString("hex"), + "ba4fa95fea1402e4876abf1d5a510615aab374ee48bb76f5230798a7d3f2fcae6ba91ba56c6d62e6e7003ce531ff02f219cb7218dd00dd2ca650ba747f19640a", + ); }); it("signs a general message", async function () { - let signer = new UserSigner(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf")); - let verifier = new UserVerifier(UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf").generatePublicKey()); + let signer = new UserSigner( + UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"), + ); + let verifier = new UserVerifier( + UserSecretKey.fromString( + "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf", + ).generatePublicKey(), + ); const message = new TestMessage({ foo: "hello", - bar: "world" + bar: "world", }); const data = message.serializeForSigning(); @@ -379,27 +467,51 @@ describe("test user wallets", () => { const keyFileObjectWithMnemonic = await loadTestKeystore("withDummyMnemonic.json"); const keyFileObjectWithSecretKey = await loadTestKeystore("withDummySecretKey.json"); - assert.equal(UserSigner.fromWallet(keyFileObjectWithoutKind, password).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.equal(UserSigner.fromWallet(keyFileObjectWithSecretKey, password).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress().bech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); - assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress().bech32(), "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"); - assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().bech32(), "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithoutKind, password).getAddress().bech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithMnemonic, password).getAddress().bech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithSecretKey, password).getAddress().bech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress().bech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress().bech32(), + "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx", + ); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress().bech32(), + "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", + ); - assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress("test").bech32(), "test1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ss5hqhtr"); - assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress("xerd").bech32(), "xerd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruq9thc9j"); - assert.equal(UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress("yerd").bech32(), "yerd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaqgh23pp"); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 0).getAddress("test").bech32(), + "test1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ss5hqhtr", + ); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 1).getAddress("xerd").bech32(), + "xerd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruq9thc9j", + ); + assert.equal( + UserSigner.fromWallet(keyFileObjectWithMnemonic, password, 2).getAddress("yerd").bech32(), + "yerd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaqgh23pp", + ); }); it("should throw error when decrypting secret key with keystore-mnemonic file", async function () { - const userWallet = UserWallet.fromMnemonic({ - mnemonic: DummyMnemonic, - password: `` - }); + const userWallet = UserWallet.fromMnemonic({ mnemonic: dummyMnemonic, password: `` }); const keystoreMnemonic = userWallet.toJSON(); assert.throws(() => { - UserWallet.decryptSecretKey(keystoreMnemonic, ``) + UserWallet.decryptSecretKey(keystoreMnemonic, ``); }, `Expected keystore kind to be secretKey, but it was mnemonic.`); }); }); From e944d9fad69aa6b94624f99e0bb8bf3c84aa72bd Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 10 Oct 2024 12:44:05 +0300 Subject: [PATCH 100/118] Remove unused signature class --- src/signature.ts | 2 +- src/wallet/signature.ts | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 src/wallet/signature.ts diff --git a/src/signature.ts b/src/signature.ts index 95fe99778..a0b872f65 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -8,7 +8,7 @@ const SIGNATURE_LENGTH = 64; export class Signature { private valueHex: string = ""; - constructor(value?: string | Buffer) { + constructor(value?: string | Buffer | Uint8Array) { if (!value) { return; } diff --git a/src/wallet/signature.ts b/src/wallet/signature.ts deleted file mode 100644 index a99620ed3..000000000 --- a/src/wallet/signature.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Signature, as an immutable object. - */ -export class Signature { - private readonly buffer: Buffer; - - constructor(buffer: Buffer | Uint8Array) { - this.buffer = Buffer.from(buffer); - } - - hex() { - return this.buffer.toString("hex"); - } -} From 4a35ef66ceee5c2c1e27f7f0f3ee503ce465daec Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 10 Oct 2024 15:32:36 +0300 Subject: [PATCH 101/118] Merge error files --- .vscode/settings.json | 1 + src/errors.ts | 64 +++++++++++++++++++ src/networkProviders/apiNetworkProvider.ts | 31 +++++++--- src/networkProviders/errors.ts | 39 ------------ src/networkProviders/index.ts | 4 +- src/networkProviders/proxyNetworkProvider.ts | 52 +++++++++++----- src/networkProviders/transactionLogs.ts | 18 ++++-- src/wallet/assertions.ts | 2 +- src/wallet/crypto/decryptor.ts | 36 +++++------ src/wallet/errors.ts | 65 -------------------- src/wallet/mnemonic.ts | 2 +- src/wallet/pem.spec.ts | 2 +- src/wallet/pem.ts | 15 +++-- src/wallet/userSigner.ts | 2 +- src/wallet/userWallet.ts | 43 ++++++++----- src/wallet/users.spec.ts | 2 +- src/wallet/validatorKeys.ts | 2 +- src/wallet/validatorSigner.ts | 2 +- 18 files changed, 198 insertions(+), 184 deletions(-) delete mode 100644 src/networkProviders/errors.ts delete mode 100644 src/wallet/errors.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 9a9afd464..37681e68f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,4 +10,5 @@ "editor.codeActionsOnSave": { "source.organizeImports": "explicit" }, + "prettier.printWidth": 120 } diff --git a/src/errors.ts b/src/errors.ts index 2bb4ff22a..e09ab86e0 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -384,3 +384,67 @@ export class ErrSmartContractQuery extends Err { this.returnCode = returnCode; } } + +/** + * Signals a wrong mnemonic format. + */ +export class ErrWrongMnemonic extends Err { + public constructor() { + super("Wrong mnemonic format"); + } +} + +/** + * Signals a bad mnemonic entropy. + */ +export class ErrBadMnemonicEntropy extends Err { + public constructor(inner: Error) { + super("Bad mnemonic entropy", inner); + } +} + +/** + * Signals a bad PEM file. + */ +export class ErrBadPEM extends Err { + public constructor(message?: string) { + super(message ? `Bad PEM: ${message}` : `Bad PEM`); + } +} + +/** + * Signals an error related to signing a message (a transaction). + */ +export class ErrSignerCannotSign extends Err { + public constructor(inner: Error) { + super(`Cannot sign`, inner); + } +} + +/** + * Signals a bad address. + */ +export class ErrBadAddress extends Err { + public constructor(value: string, inner?: Error) { + super(`Bad address: ${value}`, inner); + } +} + +/** + * Signals an error that happened during a request against the Network. + */ +export class ErrNetworkProvider extends Err { + public constructor(url: string, error: string, inner?: Error) { + let message = `Request error on url [${url}]: [${error}]`; + super(message, inner); + } +} + +/** + * Signals a generic error in the context of querying Smart Contracts. + */ +export class ErrContractQuery extends Err { + public constructor(originalError: Error) { + super(originalError.message.replace("executeQuery:", "")); + } +} diff --git a/src/networkProviders/apiNetworkProvider.ts b/src/networkProviders/apiNetworkProvider.ts index 3d6bc5565..709cb1cfe 100644 --- a/src/networkProviders/apiNetworkProvider.ts +++ b/src/networkProviders/apiNetworkProvider.ts @@ -1,10 +1,10 @@ import axios from "axios"; +import { ErrContractQuery, ErrNetworkProvider } from "../errors"; import { AccountOnNetwork, GuardianData } from "./accounts"; import { defaultAxiosConfig, defaultPagination } from "./config"; import { BaseUserAgent } from "./constants"; import { ContractQueryRequest } from "./contractQueryRequest"; import { ContractQueryResponse } from "./contractQueryResponse"; -import { ErrContractQuery, ErrNetworkProvider } from "./errors"; import { IAddress, IContractQuery, INetworkProvider, IPagination, ITransaction, ITransactionNext } from "./interface"; import { NetworkConfig } from "./networkConfig"; import { NetworkGeneralStatistics } from "./networkGeneralStatistics"; @@ -25,7 +25,7 @@ export class ApiNetworkProvider implements INetworkProvider { private url: string; private config: NetworkProviderConfig; private backingProxyNetworkProvider; - private userAgentPrefix = `${BaseUserAgent}/api` + private userAgentPrefix = `${BaseUserAgent}/api`; constructor(url: string, config?: NetworkProviderConfig) { this.url = url; @@ -71,37 +71,50 @@ export class ApiNetworkProvider implements INetworkProvider { return await this.backingProxyNetworkProvider.getGuardianData(address); } - async getFungibleTokensOfAccount(address: IAddress, pagination?: IPagination): Promise { + async getFungibleTokensOfAccount( + address: IAddress, + pagination?: IPagination, + ): Promise { pagination = pagination || defaultPagination; let url = `accounts/${address.bech32()}/tokens?${this.buildPaginationParams(pagination)}`; let response: any[] = await this.doGetGeneric(url); - let tokens = response.map(item => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item)); + let tokens = response.map((item) => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item)); // TODO: Fix sorting tokens.sort((a, b) => a.identifier.localeCompare(b.identifier)); return tokens; } - async getNonFungibleTokensOfAccount(address: IAddress, pagination?: IPagination): Promise { + async getNonFungibleTokensOfAccount( + address: IAddress, + pagination?: IPagination, + ): Promise { pagination = pagination || defaultPagination; let url = `accounts/${address.bech32()}/nfts?${this.buildPaginationParams(pagination)}`; let response: any[] = await this.doGetGeneric(url); - let tokens = response.map(item => NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(item)); + let tokens = response.map((item) => NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(item)); // TODO: Fix sorting tokens.sort((a, b) => a.identifier.localeCompare(b.identifier)); return tokens; } - async getFungibleTokenOfAccount(address: IAddress, tokenIdentifier: string): Promise { + async getFungibleTokenOfAccount( + address: IAddress, + tokenIdentifier: string, + ): Promise { let response = await this.doGetGeneric(`accounts/${address.bech32()}/tokens/${tokenIdentifier}`); let tokenData = FungibleTokenOfAccountOnNetwork.fromHttpResponse(response); return tokenData; } - async getNonFungibleTokenOfAccount(address: IAddress, collection: string, nonce: number): Promise { + async getNonFungibleTokenOfAccount( + address: IAddress, + collection: string, + nonce: number, + ): Promise { let nonceAsHex = new Nonce(nonce).hex(); let response = await this.doGetGeneric(`accounts/${address.bech32()}/nfts/${collection}-${nonceAsHex}`); let tokenData = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response); @@ -116,7 +129,7 @@ export class ApiNetworkProvider implements INetworkProvider { let response: any[] = await this.doGetGeneric(url); - return response.map(item => PairOnNetwork.fromApiHttpResponse(item)); + return response.map((item) => PairOnNetwork.fromApiHttpResponse(item)); } async getTransaction(txHash: string): Promise { diff --git a/src/networkProviders/errors.ts b/src/networkProviders/errors.ts deleted file mode 100644 index 6b297bfd1..000000000 --- a/src/networkProviders/errors.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * The base class for exceptions (errors). - */ -export class Err extends Error { - inner: Error | undefined = undefined; - - public constructor(message: string, inner?: Error) { - super(message); - this.inner = inner; - } -} - -/** - * Signals an unexpected condition. - */ -export class ErrUnexpectedCondition extends Err { - public constructor(message: string) { - super(`Unexpected condition: [${message}]`); - } -} - -/** - * Signals an error that happened during a request against the Network. - */ -export class ErrNetworkProvider extends Err { - public constructor(url: string, error: string, inner?: Error) { - let message = `Request error on url [${url}]: [${error}]`; - super(message, inner); - } -} - -/** - * Signals a generic error in the context of querying Smart Contracts. - */ -export class ErrContractQuery extends Err { - public constructor(originalError: Error) { - super(originalError.message.replace("executeQuery:", "")); - } -} diff --git a/src/networkProviders/index.ts b/src/networkProviders/index.ts index 16c7fccaa..99c22853f 100644 --- a/src/networkProviders/index.ts +++ b/src/networkProviders/index.ts @@ -5,14 +5,14 @@ export { AccountOnNetwork } from "./accounts"; export { ContractQueryResponse } from "./contractQueryResponse"; export { ContractResultItem, ContractResults } from "./contractResults"; export { - TransactionEvent as TransactionEventOnNetwork, TransactionEventData, + TransactionEvent as TransactionEventOnNetwork, TransactionEventTopic, } from "./transactionEvents"; export { TransactionLogs as TransactionLogsOnNetwork } from "./transactionLogs"; export { TransactionReceipt } from "./transactionReceipt"; -export { TransactionStatus } from "./transactionStatus"; export { TransactionOnNetwork } from "./transactions"; +export { TransactionStatus } from "./transactionStatus"; export { DefinitionOfFungibleTokenOnNetwork, DefinitionOfTokenCollectionOnNetwork } from "./tokenDefinitions"; export { FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork } from "./tokens"; diff --git a/src/networkProviders/proxyNetworkProvider.ts b/src/networkProviders/proxyNetworkProvider.ts index c58e4bf15..5d06b5173 100644 --- a/src/networkProviders/proxyNetworkProvider.ts +++ b/src/networkProviders/proxyNetworkProvider.ts @@ -1,13 +1,14 @@ import axios from "axios"; +import { ErrContractQuery, ErrNetworkProvider } from "../errors"; import { AccountOnNetwork, GuardianData } from "./accounts"; import { defaultAxiosConfig } from "./config"; -import { EsdtContractAddress, BaseUserAgent } from "./constants"; +import { BaseUserAgent, EsdtContractAddress } from "./constants"; import { ContractQueryRequest } from "./contractQueryRequest"; import { ContractQueryResponse } from "./contractQueryResponse"; -import { ErrContractQuery, ErrNetworkProvider } from "./errors"; import { IAddress, IContractQuery, INetworkProvider, IPagination, ITransaction, ITransactionNext } from "./interface"; import { NetworkConfig } from "./networkConfig"; import { NetworkGeneralStatistics } from "./networkGeneralStatistics"; +import { NetworkProviderConfig } from "./networkProviderConfig"; import { NetworkStake } from "./networkStake"; import { NetworkStatus } from "./networkStatus"; import { DefinitionOfFungibleTokenOnNetwork, DefinitionOfTokenCollectionOnNetwork } from "./tokenDefinitions"; @@ -15,13 +16,12 @@ import { FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork } f import { TransactionOnNetwork, prepareTransactionForBroadcasting } from "./transactions"; import { TransactionStatus } from "./transactionStatus"; import { extendUserAgent } from "./userAgent"; -import { NetworkProviderConfig } from "./networkProviderConfig"; // TODO: Find & remove duplicate code between "ProxyNetworkProvider" and "ApiNetworkProvider". export class ProxyNetworkProvider implements INetworkProvider { private url: string; private config: NetworkProviderConfig; - private userAgentPrefix = `${BaseUserAgent}/proxy` + private userAgentPrefix = `${BaseUserAgent}/proxy`; constructor(url: string, config?: NetworkProviderConfig) { this.url = url; @@ -65,40 +65,57 @@ export class ProxyNetworkProvider implements INetworkProvider { return accountGuardian; } - async getFungibleTokensOfAccount(address: IAddress, _pagination?: IPagination): Promise { + async getFungibleTokensOfAccount( + address: IAddress, + _pagination?: IPagination, + ): Promise { let url = `address/${address.bech32()}/esdt`; let response = await this.doGetGeneric(url); let responseItems: any[] = Object.values(response.esdts); // Skip NFTs / SFTs. - let responseItemsFiltered = responseItems.filter(item => !item.nonce); - let tokens = responseItemsFiltered.map(item => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item)); + let responseItemsFiltered = responseItems.filter((item) => !item.nonce); + let tokens = responseItemsFiltered.map((item) => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item)); // TODO: Fix sorting tokens.sort((a, b) => a.identifier.localeCompare(b.identifier)); return tokens; } - async getNonFungibleTokensOfAccount(address: IAddress, _pagination?: IPagination): Promise { + async getNonFungibleTokensOfAccount( + address: IAddress, + _pagination?: IPagination, + ): Promise { let url = `address/${address.bech32()}/esdt`; let response = await this.doGetGeneric(url); let responseItems: any[] = Object.values(response.esdts); // Skip fungible tokens. - let responseItemsFiltered = responseItems.filter(item => item.nonce >= 0); - let tokens = responseItemsFiltered.map(item => NonFungibleTokenOfAccountOnNetwork.fromProxyHttpResponse(item)); + let responseItemsFiltered = responseItems.filter((item) => item.nonce >= 0); + let tokens = responseItemsFiltered.map((item) => + NonFungibleTokenOfAccountOnNetwork.fromProxyHttpResponse(item), + ); // TODO: Fix sorting tokens.sort((a, b) => a.identifier.localeCompare(b.identifier)); return tokens; } - async getFungibleTokenOfAccount(address: IAddress, tokenIdentifier: string): Promise { + async getFungibleTokenOfAccount( + address: IAddress, + tokenIdentifier: string, + ): Promise { let response = await this.doGetGeneric(`address/${address.bech32()}/esdt/${tokenIdentifier}`); let tokenData = FungibleTokenOfAccountOnNetwork.fromHttpResponse(response.tokenData); return tokenData; } - async getNonFungibleTokenOfAccount(address: IAddress, collection: string, nonce: number): Promise { - let response = await this.doGetGeneric(`address/${address.bech32()}/nft/${collection}/nonce/${nonce.valueOf()}`); + async getNonFungibleTokenOfAccount( + address: IAddress, + collection: string, + nonce: number, + ): Promise { + let response = await this.doGetGeneric( + `address/${address.bech32()}/nft/${collection}/nonce/${nonce.valueOf()}`, + ); let tokenData = NonFungibleTokenOfAccountOnNetwork.fromProxyHttpResponseByNonce(response.tokenData); return tokenData; } @@ -133,7 +150,7 @@ export class ProxyNetworkProvider implements INetworkProvider { } async sendTransactions(txs: (ITransaction | ITransactionNext)[]): Promise { - const data = (txs).map((tx) => prepareTransactionForBroadcasting(tx)); + const data = txs.map((tx) => prepareTransactionForBroadcasting(tx)); const response = await this.doPostGeneric("transaction/send-multiple", data); const hashes = Array(txs.length).fill(null); @@ -163,7 +180,10 @@ export class ProxyNetworkProvider implements INetworkProvider { async getDefinitionOfFungibleToken(tokenIdentifier: string): Promise { let properties = await this.getTokenProperties(tokenIdentifier); - let definition = DefinitionOfFungibleTokenOnNetwork.fromResponseOfGetTokenProperties(tokenIdentifier, properties); + let definition = DefinitionOfFungibleTokenOnNetwork.fromResponseOfGetTokenProperties( + tokenIdentifier, + properties, + ); return definition; } @@ -173,7 +193,7 @@ export class ProxyNetworkProvider implements INetworkProvider { let queryResponse = await this.queryContract({ address: EsdtContractAddress, func: "getTokenProperties", - getEncodedArguments: () => [encodedIdentifier] + getEncodedArguments: () => [encodedIdentifier], }); let properties = queryResponse.getReturnDataParts(); diff --git a/src/networkProviders/transactionLogs.ts b/src/networkProviders/transactionLogs.ts index 9e7a6ef18..36f5d021d 100644 --- a/src/networkProviders/transactionLogs.ts +++ b/src/networkProviders/transactionLogs.ts @@ -1,4 +1,4 @@ -import { ErrUnexpectedCondition } from "./errors"; +import { ErrUnexpectedCondition } from "./../errors"; import { IAddress } from "./interface"; import { Address } from "./primitives"; import { TransactionEvent } from "./transactionEvents"; @@ -15,11 +15,14 @@ export class TransactionLogs { let result = new TransactionLogs(); result.address = new Address(logs.address); result.events = (logs.events || []).map((event: any) => TransactionEvent.fromHttpResponse(event)); - + return result; } - findSingleOrNoneEvent(identifier: string, predicate?: (event: TransactionEvent) => boolean): TransactionEvent | undefined { + findSingleOrNoneEvent( + identifier: string, + predicate?: (event: TransactionEvent) => boolean, + ): TransactionEvent | undefined { let events = this.findEvents(identifier, predicate); if (events.length > 1) { @@ -29,15 +32,18 @@ export class TransactionLogs { return events[0]; } - findFirstOrNoneEvent(identifier: string, predicate?: (event: TransactionEvent) => boolean): TransactionEvent | undefined { + findFirstOrNoneEvent( + identifier: string, + predicate?: (event: TransactionEvent) => boolean, + ): TransactionEvent | undefined { return this.findEvents(identifier, predicate)[0]; } findEvents(identifier: string, predicate?: (event: TransactionEvent) => boolean): TransactionEvent[] { - let events = this.events.filter(event => event.identifier == identifier); + let events = this.events.filter((event) => event.identifier == identifier); if (predicate) { - events = events.filter(event => predicate(event)); + events = events.filter((event) => predicate(event)); } return events; diff --git a/src/wallet/assertions.ts b/src/wallet/assertions.ts index 80602639c..09ac481d2 100644 --- a/src/wallet/assertions.ts +++ b/src/wallet/assertions.ts @@ -1,4 +1,4 @@ -import { ErrInvariantFailed } from "./errors"; +import { ErrInvariantFailed } from "../errors"; export function guardLength(withLength: { length?: number }, expectedLength: number) { let actualLength = withLength.length || 0; diff --git a/src/wallet/crypto/decryptor.ts b/src/wallet/crypto/decryptor.ts index 0a42be1b0..388b9fd63 100644 --- a/src/wallet/crypto/decryptor.ts +++ b/src/wallet/crypto/decryptor.ts @@ -1,27 +1,27 @@ import crypto from "crypto"; -import { EncryptedData } from "./encryptedData"; +import { Err } from "../../errors"; import { DigestAlgorithm } from "./constants"; -import { Err } from "../errors"; +import { EncryptedData } from "./encryptedData"; 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); + 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; + 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"); - } + if (computedMAC.toString("hex") !== actualMAC) { + throw new Err("MAC mismatch, possibly wrong password"); + } - const decipher = crypto.createDecipheriv(data.cipher, derivedKeyFirstHalf, iv); + const decipher = crypto.createDecipheriv(data.cipher, derivedKeyFirstHalf, iv); - return Buffer.concat([decipher.update(ciphertext), decipher.final()]); - } + return Buffer.concat([decipher.update(ciphertext), decipher.final()]); + } } diff --git a/src/wallet/errors.ts b/src/wallet/errors.ts deleted file mode 100644 index 4c3ea2ecd..000000000 --- a/src/wallet/errors.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * The base class for exceptions (errors). - */ -export class Err extends Error { - inner: Error | undefined = undefined; - - public constructor(message: string, inner?: Error) { - super(message); - this.inner = inner; - } -} - -/** - * Signals that an invariant failed. - */ -export class ErrInvariantFailed extends Err { - public constructor(message: string) { - super(`"Invariant failed: ${message}`); - } -} - -/** - * Signals a wrong mnemonic format. - */ -export class ErrWrongMnemonic extends Err { - public constructor() { - super("Wrong mnemonic format"); - } -} - -/** - * Signals a bad mnemonic entropy. - */ -export class ErrBadMnemonicEntropy extends Err { - public constructor(inner: Error) { - super("Bad mnemonic entropy", inner); - } -} - -/** - * Signals a bad PEM file. - */ -export class ErrBadPEM extends Err { - public constructor(message?: string) { - super(message ? `Bad PEM: ${message}` : `Bad PEM`); - } -} - -/** - * Signals an error related to signing a message (a transaction). - */ -export class ErrSignerCannotSign extends Err { - public constructor(inner: Error) { - super(`Cannot sign`, inner); - } -} - -/** - * Signals a bad address. - */ -export class ErrBadAddress extends Err { - public constructor(value: string, inner?: Error) { - super(`Bad address: ${value}`, inner); - } -} diff --git a/src/wallet/mnemonic.ts b/src/wallet/mnemonic.ts index 93b62ee3a..8e4b2baa5 100644 --- a/src/wallet/mnemonic.ts +++ b/src/wallet/mnemonic.ts @@ -1,6 +1,6 @@ import { entropyToMnemonic, generateMnemonic, mnemonicToEntropy, mnemonicToSeedSync, validateMnemonic } from "bip39"; import { derivePath } from "ed25519-hd-key"; -import { ErrBadMnemonicEntropy, ErrWrongMnemonic } from "./errors"; +import { ErrBadMnemonicEntropy, ErrWrongMnemonic } from "../errors"; import { UserSecretKey } from "./userKeys"; const MNEMONIC_STRENGTH = 256; diff --git a/src/wallet/pem.spec.ts b/src/wallet/pem.spec.ts index 538a99cfc..73eda6b96 100644 --- a/src/wallet/pem.spec.ts +++ b/src/wallet/pem.spec.ts @@ -1,7 +1,7 @@ import { Buffer } from "buffer"; import { assert } from "chai"; +import { ErrBadPEM } from "../errors"; import { loadTestWallet, TestWallet } from "./../testutils/wallets"; -import { ErrBadPEM } from "./errors"; import { parse, parseUserKey, parseValidatorKey } from "./pem"; import { BLS } from "./validatorKeys"; diff --git a/src/wallet/pem.ts b/src/wallet/pem.ts index f9add5494..47a839a1f 100644 --- a/src/wallet/pem.ts +++ b/src/wallet/pem.ts @@ -1,6 +1,6 @@ -import { ErrBadPEM } from "./errors"; -import { UserSecretKey, USER_PUBKEY_LENGTH, USER_SEED_LENGTH } from "./userKeys"; -import { ValidatorSecretKey, VALIDATOR_SECRETKEY_LENGTH } from "./validatorKeys"; +import { ErrBadPEM } from "../errors"; +import { USER_PUBKEY_LENGTH, USER_SEED_LENGTH, UserSecretKey } from "./userKeys"; +import { VALIDATOR_SECRETKEY_LENGTH, ValidatorSecretKey } from "./validatorKeys"; export function parseUserKey(text: string, index: number = 0): UserSecretKey { let keys = parseUserKeys(text); @@ -10,7 +10,7 @@ export function parseUserKey(text: string, index: number = 0): UserSecretKey { export function parseUserKeys(text: string): UserSecretKey[] { // The user PEM files encode both the seed and the pubkey in their payloads. let buffers = parse(text, USER_SEED_LENGTH + USER_PUBKEY_LENGTH); - return buffers.map(buffer => new UserSecretKey(buffer.slice(0, USER_SEED_LENGTH))); + return buffers.map((buffer) => new UserSecretKey(buffer.slice(0, USER_SEED_LENGTH))); } export function parseValidatorKey(text: string, index: number = 0): ValidatorSecretKey { @@ -20,12 +20,15 @@ export function parseValidatorKey(text: string, index: number = 0): ValidatorSec export function parseValidatorKeys(text: string): ValidatorSecretKey[] { let buffers = parse(text, VALIDATOR_SECRETKEY_LENGTH); - return buffers.map(buffer => new ValidatorSecretKey(buffer)); + return buffers.map((buffer) => new ValidatorSecretKey(buffer)); } export function parse(text: string, expectedLength: number): Buffer[] { // Split by newlines, trim whitespace, then discard remaining empty lines. - let lines = text.split(/\r?\n/).map(line => line.trim()).filter(line => line.length > 0); + let lines = text + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0); let buffers: Buffer[] = []; let linesAccumulator: string[] = []; diff --git a/src/wallet/userSigner.ts b/src/wallet/userSigner.ts index 37f05e302..acc8f6f09 100644 --- a/src/wallet/userSigner.ts +++ b/src/wallet/userSigner.ts @@ -1,5 +1,5 @@ import { Address } from "../address"; -import { ErrSignerCannotSign } from "./errors"; +import { ErrSignerCannotSign } from "../errors"; import { UserSecretKey } from "./userKeys"; import { UserWallet } from "./userWallet"; diff --git a/src/wallet/userWallet.ts b/src/wallet/userWallet.ts index 8c7ff833b..743f4ac69 100644 --- a/src/wallet/userWallet.ts +++ b/src/wallet/userWallet.ts @@ -1,6 +1,6 @@ +import { Err } from "../errors"; import { CipherAlgorithm, Decryptor, EncryptedData, Encryptor, KeyDerivationFunction, Randomness } from "./crypto"; import { ScryptKeyDerivationParams } from "./crypto/derivationParams"; -import { Err } from "./errors"; import { Mnemonic } from "./mnemonic"; import { UserPublicKey, UserSecretKey } from "./userKeys"; @@ -12,7 +12,7 @@ interface IRandomness { export enum UserWalletKind { SecretKey = "secretKey", - Mnemonic = "mnemonic" + Mnemonic = "mnemonic", } export class UserWallet { @@ -20,7 +20,11 @@ export class UserWallet { private readonly encryptedData: EncryptedData; private readonly publicKeyWhenKindIsSecretKey?: UserPublicKey; - private constructor({ kind, encryptedData, publicKeyWhenKindIsSecretKey }: { + private constructor({ + kind, + encryptedData, + publicKeyWhenKindIsSecretKey, + }: { kind: UserWalletKind; encryptedData: EncryptedData; publicKeyWhenKindIsSecretKey?: UserPublicKey; @@ -30,7 +34,11 @@ export class UserWallet { this.publicKeyWhenKindIsSecretKey = publicKeyWhenKindIsSecretKey; } - static fromSecretKey({ secretKey, password, randomness }: { + static fromSecretKey({ + secretKey, + password, + randomness, + }: { secretKey: UserSecretKey; password: string; randomness?: IRandomness; @@ -44,11 +52,15 @@ export class UserWallet { return new UserWallet({ kind: UserWalletKind.SecretKey, encryptedData, - publicKeyWhenKindIsSecretKey: publicKey + publicKeyWhenKindIsSecretKey: publicKey, }); } - static fromMnemonic({ mnemonic, password, randomness }: { + static fromMnemonic({ + mnemonic, + password, + randomness, + }: { mnemonic: string; password: string; randomness?: IRandomness; @@ -61,7 +73,7 @@ export class UserWallet { return new UserWallet({ kind: UserWalletKind.Mnemonic, - encryptedData + encryptedData, }); } @@ -86,18 +98,18 @@ export class UserWallet { /** * Copied from: https://github.com/multiversx/mx-deprecated-core-js/blob/v1.28.0/src/account.js#L42 - * Notes: adjustements (code refactoring, no change in logic), in terms of: + * Notes: adjustements (code refactoring, no change in logic), in terms of: * - typing (since this is the TypeScript version) * - error handling (in line with sdk-core's error system) * - references to crypto functions * - references to object members - * + * * From an encrypted keyfile, given the password, loads the secret key and the public key. */ static decryptSecretKey(keyFileObject: any, password: string): UserSecretKey { // Here, we check the "kind" field only for files that have it. Older keystore files (holding only secret keys) do not have this field. const kind = keyFileObject.kind; - if (kind && kind !== UserWalletKind.SecretKey){ + if (kind && kind !== UserWalletKind.SecretKey) { throw new Err(`Expected keystore kind to be ${UserWalletKind.SecretKey}, but it was ${kind}.`); } @@ -120,7 +132,7 @@ export class UserWallet { const encryptedData = UserWallet.edFromJSON(keyFileObject); const data = Decryptor.decrypt(encryptedData, password); - const mnemonic = Mnemonic.fromString(data.toString()) + const mnemonic = Mnemonic.fromString(data.toString()); return mnemonic; } @@ -136,7 +148,7 @@ export class UserWallet { keyfileObject.crypto.kdfparams.n, keyfileObject.crypto.kdfparams.r, keyfileObject.crypto.kdfparams.p, - keyfileObject.crypto.kdfparams.dklen + keyfileObject.crypto.kdfparams.dklen, ), salt: keyfileObject.crypto.kdfparams.salt, mac: keyfileObject.crypto.mac, @@ -167,7 +179,7 @@ export class UserWallet { id: this.encryptedData.id, address: this.publicKeyWhenKindIsSecretKey.hex(), bech32: this.publicKeyWhenKindIsSecretKey.toAddress(addressHrp).toString(), - crypto: cryptoSection + crypto: cryptoSection, }; return envelope; @@ -184,7 +196,7 @@ export class UserWallet { salt: this.encryptedData.salt, n: this.encryptedData.kdfparams.n, r: this.encryptedData.kdfparams.r, - p: this.encryptedData.kdfparams.p + p: this.encryptedData.kdfparams.p, }, mac: this.encryptedData.mac, }; @@ -199,8 +211,7 @@ export class UserWallet { version: this.encryptedData.version, id: this.encryptedData.id, kind: this.kind, - crypto: cryptoSection + crypto: cryptoSection, }; } } - diff --git a/src/wallet/users.spec.ts b/src/wallet/users.spec.ts index 815c0000e..976f4ef85 100644 --- a/src/wallet/users.spec.ts +++ b/src/wallet/users.spec.ts @@ -1,4 +1,5 @@ import { assert } from "chai"; +import { ErrBadMnemonicEntropy, ErrInvariantFailed } from "../errors"; import { TestMessage } from "./../testutils/message"; import { TestTransaction } from "./../testutils/transaction"; import { @@ -10,7 +11,6 @@ import { TestWallet, } from "./../testutils/wallets"; import { Randomness } from "./crypto"; -import { ErrBadMnemonicEntropy, ErrInvariantFailed } from "./errors"; import { Mnemonic } from "./mnemonic"; import { UserSecretKey } from "./userKeys"; import { UserSigner } from "./userSigner"; diff --git a/src/wallet/validatorKeys.ts b/src/wallet/validatorKeys.ts index 7425e113d..d91bc315a 100644 --- a/src/wallet/validatorKeys.ts +++ b/src/wallet/validatorKeys.ts @@ -1,5 +1,5 @@ +import { ErrInvariantFailed } from "../errors"; import { guardLength } from "./assertions"; -import { ErrInvariantFailed } from "./errors"; import { parseValidatorKey } from "./pem"; const bls = require("@multiversx/sdk-bls-wasm"); diff --git a/src/wallet/validatorSigner.ts b/src/wallet/validatorSigner.ts index 8c0c028f8..161c2dad1 100644 --- a/src/wallet/validatorSigner.ts +++ b/src/wallet/validatorSigner.ts @@ -1,4 +1,4 @@ -import { ErrSignerCannotSign } from "./errors"; +import { ErrSignerCannotSign } from "../errors"; import { BLS, ValidatorSecretKey } from "./validatorKeys"; /** From 78bcbb4e7f7687208c6dcdbaa80d8dde0441f15e Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 10 Oct 2024 16:03:15 +0300 Subject: [PATCH 102/118] Add handle for Uint8Array on signature --- src/signature.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/signature.ts b/src/signature.ts index a0b872f65..e9ab00f24 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -15,8 +15,9 @@ export class Signature { if (typeof value === "string") { return Signature.fromHex(value); } - if (value instanceof Buffer) { - return Signature.fromBuffer(value); + + if (ArrayBuffer.isView(value)) { + return Signature.fromBuffer(Buffer.from(value)); } } From 23dbb8661b7bfab8c9124102e8b55df8817ca6f5 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 10 Oct 2024 16:28:29 +0300 Subject: [PATCH 103/118] Change let to const --- src/errors.ts | 8 +- src/networkProviders/apiNetworkProvider.ts | 78 +++++++++--------- src/networkProviders/proxyNetworkProvider.ts | 87 ++++++++++---------- src/wallet/pem.ts | 18 ++-- 4 files changed, 97 insertions(+), 94 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index e09ab86e0..a0a47f0ab 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -13,7 +13,7 @@ export class Err extends Error { * Returns a pretty, friendly summary for the error or for the chain of errros (if appropriate). */ summary(): any[] { - let result = []; + const result = []; result.push({ name: this.name, message: this.message }); @@ -77,7 +77,7 @@ export class ErrUnexpectedCondition extends Err { */ export class ErrAddressCannotCreate extends Err { public constructor(input: any, inner?: Error) { - let message = `Cannot create address from: ${input}`; + const message = `Cannot create address from: ${input}`; super(message, inner); } } @@ -141,7 +141,7 @@ export class ErrTransactionOptionsInvalid extends Err { */ export class ErrSignatureCannotCreate extends Err { public constructor(input: any, inner?: Error) { - let message = `Cannot create signature from: ${input}`; + const message = `Cannot create signature from: ${input}`; super(message, inner); } } @@ -435,7 +435,7 @@ export class ErrBadAddress extends Err { */ export class ErrNetworkProvider extends Err { public constructor(url: string, error: string, inner?: Error) { - let message = `Request error on url [${url}]: [${error}]`; + const message = `Request error on url [${url}]: [${error}]`; super(message, inner); } } diff --git a/src/networkProviders/apiNetworkProvider.ts b/src/networkProviders/apiNetworkProvider.ts index 709cb1cfe..0b777c35c 100644 --- a/src/networkProviders/apiNetworkProvider.ts +++ b/src/networkProviders/apiNetworkProvider.ts @@ -29,7 +29,7 @@ export class ApiNetworkProvider implements INetworkProvider { constructor(url: string, config?: NetworkProviderConfig) { this.url = url; - let proxyConfig = this.getProxyConfig(config); + const proxyConfig = this.getProxyConfig(config); this.config = { ...defaultAxiosConfig, ...config }; this.backingProxyNetworkProvider = new ProxyNetworkProvider(url, proxyConfig); extendUserAgent(this.userAgentPrefix, this.config); @@ -50,20 +50,20 @@ export class ApiNetworkProvider implements INetworkProvider { } async getNetworkStakeStatistics(): Promise { - let response = await this.doGetGeneric("stake"); - let networkStake = NetworkStake.fromHttpResponse(response); + const response = await this.doGetGeneric("stake"); + const networkStake = NetworkStake.fromHttpResponse(response); return networkStake; } async getNetworkGeneralStatistics(): Promise { - let response = await this.doGetGeneric("stats"); - let stats = NetworkGeneralStatistics.fromHttpResponse(response); + const response = await this.doGetGeneric("stats"); + const stats = NetworkGeneralStatistics.fromHttpResponse(response); return stats; } async getAccount(address: IAddress): Promise { - let response = await this.doGetGeneric(`accounts/${address.bech32()}`); - let account = AccountOnNetwork.fromHttpResponse(response); + const response = await this.doGetGeneric(`accounts/${address.bech32()}`); + const account = AccountOnNetwork.fromHttpResponse(response); return account; } @@ -77,9 +77,9 @@ export class ApiNetworkProvider implements INetworkProvider { ): Promise { pagination = pagination || defaultPagination; - let url = `accounts/${address.bech32()}/tokens?${this.buildPaginationParams(pagination)}`; - let response: any[] = await this.doGetGeneric(url); - let tokens = response.map((item) => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item)); + const url = `accounts/${address.bech32()}/tokens?${this.buildPaginationParams(pagination)}`; + const response: any[] = await this.doGetGeneric(url); + const tokens = response.map((item) => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item)); // TODO: Fix sorting tokens.sort((a, b) => a.identifier.localeCompare(b.identifier)); @@ -92,9 +92,9 @@ export class ApiNetworkProvider implements INetworkProvider { ): Promise { pagination = pagination || defaultPagination; - let url = `accounts/${address.bech32()}/nfts?${this.buildPaginationParams(pagination)}`; - let response: any[] = await this.doGetGeneric(url); - let tokens = response.map((item) => NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(item)); + const url = `accounts/${address.bech32()}/nfts?${this.buildPaginationParams(pagination)}`; + const response: any[] = await this.doGetGeneric(url); + const tokens = response.map((item) => NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(item)); // TODO: Fix sorting tokens.sort((a, b) => a.identifier.localeCompare(b.identifier)); @@ -105,8 +105,8 @@ export class ApiNetworkProvider implements INetworkProvider { address: IAddress, tokenIdentifier: string, ): Promise { - let response = await this.doGetGeneric(`accounts/${address.bech32()}/tokens/${tokenIdentifier}`); - let tokenData = FungibleTokenOfAccountOnNetwork.fromHttpResponse(response); + const response = await this.doGetGeneric(`accounts/${address.bech32()}/tokens/${tokenIdentifier}`); + const tokenData = FungibleTokenOfAccountOnNetwork.fromHttpResponse(response); return tokenData; } @@ -115,9 +115,9 @@ export class ApiNetworkProvider implements INetworkProvider { collection: string, nonce: number, ): Promise { - let nonceAsHex = new Nonce(nonce).hex(); - let response = await this.doGetGeneric(`accounts/${address.bech32()}/nfts/${collection}-${nonceAsHex}`); - let tokenData = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response); + const nonceAsHex = new Nonce(nonce).hex(); + const response = await this.doGetGeneric(`accounts/${address.bech32()}/nfts/${collection}-${nonceAsHex}`); + const tokenData = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response); return tokenData; } @@ -127,20 +127,20 @@ export class ApiNetworkProvider implements INetworkProvider { url = `${url}?from=${pagination.from}&size=${pagination.size}`; } - let response: any[] = await this.doGetGeneric(url); + const response: any[] = await this.doGetGeneric(url); return response.map((item) => PairOnNetwork.fromApiHttpResponse(item)); } async getTransaction(txHash: string): Promise { - let response = await this.doGetGeneric(`transactions/${txHash}`); - let transaction = TransactionOnNetwork.fromApiHttpResponse(txHash, response); + const response = await this.doGetGeneric(`transactions/${txHash}`); + const transaction = TransactionOnNetwork.fromApiHttpResponse(txHash, response); return transaction; } async getTransactionStatus(txHash: string): Promise { - let response = await this.doGetGeneric(`transactions/${txHash}?fields=status`); - let status = new TransactionStatus(response.status); + const response = await this.doGetGeneric(`transactions/${txHash}?fields=status`); + const status = new TransactionStatus(response.status); return status; } @@ -160,8 +160,8 @@ export class ApiNetworkProvider implements INetworkProvider { async queryContract(query: IContractQuery): Promise { try { - let request = new ContractQueryRequest(query).toHttpRequest(); - let response = await this.doPostGeneric("query", request); + const request = new ContractQueryRequest(query).toHttpRequest(); + const response = await this.doPostGeneric("query", request); return ContractQueryResponse.fromHttpResponse(response); } catch (error: any) { throw new ErrContractQuery(error); @@ -169,31 +169,31 @@ export class ApiNetworkProvider implements INetworkProvider { } async getDefinitionOfFungibleToken(tokenIdentifier: string): Promise { - let response = await this.doGetGeneric(`tokens/${tokenIdentifier}`); - let definition = DefinitionOfFungibleTokenOnNetwork.fromApiHttpResponse(response); + const response = await this.doGetGeneric(`tokens/${tokenIdentifier}`); + const definition = DefinitionOfFungibleTokenOnNetwork.fromApiHttpResponse(response); return definition; } async getDefinitionOfTokenCollection(collection: string): Promise { - let response = await this.doGetGeneric(`collections/${collection}`); - let definition = DefinitionOfTokenCollectionOnNetwork.fromApiHttpResponse(response); + const response = await this.doGetGeneric(`collections/${collection}`); + const definition = DefinitionOfTokenCollectionOnNetwork.fromApiHttpResponse(response); return definition; } async getNonFungibleToken(collection: string, nonce: number): Promise { - let nonceAsHex = new Nonce(nonce).hex(); - let response = await this.doGetGeneric(`nfts/${collection}-${nonceAsHex}`); - let token = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response); + const nonceAsHex = new Nonce(nonce).hex(); + const response = await this.doGetGeneric(`nfts/${collection}-${nonceAsHex}`); + const token = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response); return token; } async doGetGeneric(resourceUrl: string): Promise { - let response = await this.doGet(resourceUrl); + const response = await this.doGet(resourceUrl); return response; } async doPostGeneric(resourceUrl: string, payload: any): Promise { - let response = await this.doPost(resourceUrl, payload); + const response = await this.doPost(resourceUrl, payload); return response; } @@ -202,10 +202,10 @@ export class ApiNetworkProvider implements INetworkProvider { } private async doGet(resourceUrl: string): Promise { - let url = `${this.url}/${resourceUrl}`; + const url = `${this.url}/${resourceUrl}`; try { - let response = await axios.get(url, this.config); + const response = await axios.get(url, this.config); return response.data; } catch (error) { this.handleApiError(error, resourceUrl); @@ -213,17 +213,17 @@ export class ApiNetworkProvider implements INetworkProvider { } private async doPost(resourceUrl: string, payload: any): Promise { - let url = `${this.url}/${resourceUrl}`; + const url = `${this.url}/${resourceUrl}`; try { - let response = await axios.post(url, payload, { + const response = await axios.post(url, payload, { ...this.config, headers: { "Content-Type": "application/json", ...this.config.headers, }, }); - let responsePayload = response.data; + const responsePayload = response.data; return responsePayload; } catch (error) { this.handleApiError(error, resourceUrl); diff --git a/src/networkProviders/proxyNetworkProvider.ts b/src/networkProviders/proxyNetworkProvider.ts index 5d06b5173..4a70cf14b 100644 --- a/src/networkProviders/proxyNetworkProvider.ts +++ b/src/networkProviders/proxyNetworkProvider.ts @@ -30,14 +30,14 @@ export class ProxyNetworkProvider implements INetworkProvider { } async getNetworkConfig(): Promise { - let response = await this.doGetGeneric("network/config"); - let networkConfig = NetworkConfig.fromHttpResponse(response.config); + const response = await this.doGetGeneric("network/config"); + const networkConfig = NetworkConfig.fromHttpResponse(response.config); return networkConfig; } async getNetworkStatus(): Promise { - let response = await this.doGetGeneric("network/status/4294967295"); - let networkStatus = NetworkStatus.fromHttpResponse(response.status); + const response = await this.doGetGeneric("network/status/4294967295"); + const networkStatus = NetworkStatus.fromHttpResponse(response.status); return networkStatus; } @@ -54,8 +54,8 @@ export class ProxyNetworkProvider implements INetworkProvider { } async getAccount(address: IAddress): Promise { - let response = await this.doGetGeneric(`address/${address.bech32()}`); - let account = AccountOnNetwork.fromHttpResponse(response.account); + const response = await this.doGetGeneric(`address/${address.bech32()}`); + const account = AccountOnNetwork.fromHttpResponse(response.account); return account; } @@ -69,12 +69,12 @@ export class ProxyNetworkProvider implements INetworkProvider { address: IAddress, _pagination?: IPagination, ): Promise { - let url = `address/${address.bech32()}/esdt`; - let response = await this.doGetGeneric(url); - let responseItems: any[] = Object.values(response.esdts); + const url = `address/${address.bech32()}/esdt`; + const response = await this.doGetGeneric(url); + const responseItems: any[] = Object.values(response.esdts); // Skip NFTs / SFTs. - let responseItemsFiltered = responseItems.filter((item) => !item.nonce); - let tokens = responseItemsFiltered.map((item) => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item)); + const responseItemsFiltered = responseItems.filter((item) => !item.nonce); + const tokens = responseItemsFiltered.map((item) => FungibleTokenOfAccountOnNetwork.fromHttpResponse(item)); // TODO: Fix sorting tokens.sort((a, b) => a.identifier.localeCompare(b.identifier)); @@ -85,12 +85,12 @@ export class ProxyNetworkProvider implements INetworkProvider { address: IAddress, _pagination?: IPagination, ): Promise { - let url = `address/${address.bech32()}/esdt`; - let response = await this.doGetGeneric(url); - let responseItems: any[] = Object.values(response.esdts); + const url = `address/${address.bech32()}/esdt`; + const response = await this.doGetGeneric(url); + const responseItems: any[] = Object.values(response.esdts); // Skip fungible tokens. - let responseItemsFiltered = responseItems.filter((item) => item.nonce >= 0); - let tokens = responseItemsFiltered.map((item) => + const responseItemsFiltered = responseItems.filter((item) => item.nonce >= 0); + const tokens = responseItemsFiltered.map((item) => NonFungibleTokenOfAccountOnNetwork.fromProxyHttpResponse(item), ); @@ -103,8 +103,8 @@ export class ProxyNetworkProvider implements INetworkProvider { address: IAddress, tokenIdentifier: string, ): Promise { - let response = await this.doGetGeneric(`address/${address.bech32()}/esdt/${tokenIdentifier}`); - let tokenData = FungibleTokenOfAccountOnNetwork.fromHttpResponse(response.tokenData); + const response = await this.doGetGeneric(`address/${address.bech32()}/esdt/${tokenIdentifier}`); + const tokenData = FungibleTokenOfAccountOnNetwork.fromHttpResponse(response.tokenData); return tokenData; } @@ -113,10 +113,10 @@ export class ProxyNetworkProvider implements INetworkProvider { collection: string, nonce: number, ): Promise { - let response = await this.doGetGeneric( + const response = await this.doGetGeneric( `address/${address.bech32()}/nft/${collection}/nonce/${nonce.valueOf()}`, ); - let tokenData = NonFungibleTokenOfAccountOnNetwork.fromProxyHttpResponseByNonce(response.tokenData); + const tokenData = NonFungibleTokenOfAccountOnNetwork.fromProxyHttpResponseByNonce(response.tokenData); return tokenData; } @@ -127,8 +127,8 @@ export class ProxyNetworkProvider implements INetworkProvider { processStatusPromise = this.getTransactionStatus(txHash); } - let url = this.buildUrlWithQueryParameters(`transaction/${txHash}`, { withResults: "true" }); - let response = await this.doGetGeneric(url); + const url = this.buildUrlWithQueryParameters(`transaction/${txHash}`, { withResults: "true" }); + const response = await this.doGetGeneric(url); if (processStatusPromise) { const processStatus = await processStatusPromise; @@ -138,8 +138,8 @@ export class ProxyNetworkProvider implements INetworkProvider { } async getTransactionStatus(txHash: string): Promise { - let response = await this.doGetGeneric(`transaction/${txHash}/process-status`); - let status = new TransactionStatus(response.status); + const response = await this.doGetGeneric(`transaction/${txHash}/process-status`); + const status = new TransactionStatus(response.status); return status; } @@ -170,8 +170,8 @@ export class ProxyNetworkProvider implements INetworkProvider { async queryContract(query: IContractQuery): Promise { try { - let request = new ContractQueryRequest(query).toHttpRequest(); - let response = await this.doPostGeneric("vm-values/query", request); + const request = new ContractQueryRequest(query).toHttpRequest(); + const response = await this.doPostGeneric("vm-values/query", request); return ContractQueryResponse.fromHttpResponse(response.data); } catch (error: any) { throw new ErrContractQuery(error); @@ -179,8 +179,8 @@ export class ProxyNetworkProvider implements INetworkProvider { } async getDefinitionOfFungibleToken(tokenIdentifier: string): Promise { - let properties = await this.getTokenProperties(tokenIdentifier); - let definition = DefinitionOfFungibleTokenOnNetwork.fromResponseOfGetTokenProperties( + const properties = await this.getTokenProperties(tokenIdentifier); + const definition = DefinitionOfFungibleTokenOnNetwork.fromResponseOfGetTokenProperties( tokenIdentifier, properties, ); @@ -188,21 +188,24 @@ export class ProxyNetworkProvider implements INetworkProvider { } private async getTokenProperties(identifier: string): Promise { - let encodedIdentifier = Buffer.from(identifier).toString("hex"); + const encodedIdentifier = Buffer.from(identifier).toString("hex"); - let queryResponse = await this.queryContract({ + const queryResponse = await this.queryContract({ address: EsdtContractAddress, func: "getTokenProperties", getEncodedArguments: () => [encodedIdentifier], }); - let properties = queryResponse.getReturnDataParts(); + const properties = queryResponse.getReturnDataParts(); return properties; } async getDefinitionOfTokenCollection(collection: string): Promise { - let properties = await this.getTokenProperties(collection); - let definition = DefinitionOfTokenCollectionOnNetwork.fromResponseOfGetTokenProperties(collection, properties); + const properties = await this.getTokenProperties(collection); + const definition = DefinitionOfTokenCollectionOnNetwork.fromResponseOfGetTokenProperties( + collection, + properties, + ); return definition; } @@ -211,21 +214,21 @@ export class ProxyNetworkProvider implements INetworkProvider { } async doGetGeneric(resourceUrl: string): Promise { - let response = await this.doGet(resourceUrl); + const response = await this.doGet(resourceUrl); return response; } async doPostGeneric(resourceUrl: string, payload: any): Promise { - let response = await this.doPost(resourceUrl, payload); + const response = await this.doPost(resourceUrl, payload); return response; } private async doGet(resourceUrl: string): Promise { - let url = `${this.url}/${resourceUrl}`; + const url = `${this.url}/${resourceUrl}`; try { - let response = await axios.get(url, this.config); - let payload = response.data.data; + const response = await axios.get(url, this.config); + const payload = response.data.data; return payload; } catch (error) { this.handleApiError(error, resourceUrl); @@ -233,17 +236,17 @@ export class ProxyNetworkProvider implements INetworkProvider { } private async doPost(resourceUrl: string, payload: any): Promise { - let url = `${this.url}/${resourceUrl}`; + const url = `${this.url}/${resourceUrl}`; try { - let response = await axios.post(url, payload, { + const response = await axios.post(url, payload, { ...this.config, headers: { "Content-Type": "application/json", ...this.config.headers, }, }); - let responsePayload = response.data.data; + const responsePayload = response.data.data; return responsePayload; } catch (error) { this.handleApiError(error, resourceUrl); @@ -251,7 +254,7 @@ export class ProxyNetworkProvider implements INetworkProvider { } private buildUrlWithQueryParameters(endpoint: string, params: Record): string { - let searchParams = new URLSearchParams(); + const searchParams = new URLSearchParams(); for (let [key, value] of Object.entries(params)) { if (value) { diff --git a/src/wallet/pem.ts b/src/wallet/pem.ts index 47a839a1f..b60119699 100644 --- a/src/wallet/pem.ts +++ b/src/wallet/pem.ts @@ -3,42 +3,42 @@ import { USER_PUBKEY_LENGTH, USER_SEED_LENGTH, UserSecretKey } from "./userKeys" import { VALIDATOR_SECRETKEY_LENGTH, ValidatorSecretKey } from "./validatorKeys"; export function parseUserKey(text: string, index: number = 0): UserSecretKey { - let keys = parseUserKeys(text); + const keys = parseUserKeys(text); return keys[index]; } export function parseUserKeys(text: string): UserSecretKey[] { // The user PEM files encode both the seed and the pubkey in their payloads. - let buffers = parse(text, USER_SEED_LENGTH + USER_PUBKEY_LENGTH); + const buffers = parse(text, USER_SEED_LENGTH + USER_PUBKEY_LENGTH); return buffers.map((buffer) => new UserSecretKey(buffer.slice(0, USER_SEED_LENGTH))); } export function parseValidatorKey(text: string, index: number = 0): ValidatorSecretKey { - let keys = parseValidatorKeys(text); + const keys = parseValidatorKeys(text); return keys[index]; } export function parseValidatorKeys(text: string): ValidatorSecretKey[] { - let buffers = parse(text, VALIDATOR_SECRETKEY_LENGTH); + const buffers = parse(text, VALIDATOR_SECRETKEY_LENGTH); return buffers.map((buffer) => new ValidatorSecretKey(buffer)); } export function parse(text: string, expectedLength: number): Buffer[] { // Split by newlines, trim whitespace, then discard remaining empty lines. - let lines = text + const lines = text .split(/\r?\n/) .map((line) => line.trim()) .filter((line) => line.length > 0); - let buffers: Buffer[] = []; + const buffers: Buffer[] = []; let linesAccumulator: string[] = []; for (const line of lines) { if (line.startsWith("-----BEGIN")) { linesAccumulator = []; } else if (line.startsWith("-----END")) { - let asBase64 = linesAccumulator.join(""); - let asHex = Buffer.from(asBase64, "base64").toString(); - let asBytes = Buffer.from(asHex, "hex"); + const asBase64 = linesAccumulator.join(""); + const asHex = Buffer.from(asBase64, "base64").toString(); + const asBytes = Buffer.from(asHex, "hex"); if (asBytes.length != expectedLength) { throw new ErrBadPEM(`incorrect key length: expected ${expectedLength}, found ${asBytes.length}`); From e0c7db1159bb58019e9fea47cc7a7b432c4a7198 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 10 Oct 2024 16:32:31 +0300 Subject: [PATCH 104/118] Remove primitives file --- src/networkProviders/accounts.ts | 6 +- src/networkProviders/apiNetworkProvider.ts | 6 +- src/networkProviders/constants.ts | 6 +- src/networkProviders/contractResults.ts | 10 +-- src/networkProviders/pairs.ts | 6 +- src/networkProviders/primitives.spec.ts | 17 ----- src/networkProviders/primitives.ts | 66 ------------------- .../providers.dev.net.spec.ts | 4 +- src/networkProviders/tokenDefinitions.ts | 19 +++--- src/networkProviders/tokens.ts | 7 +- src/networkProviders/transactionEvents.ts | 18 ++--- src/networkProviders/transactionLogs.ts | 4 +- src/networkProviders/transactionReceipt.ts | 12 ++-- src/networkProviders/transactions.ts | 33 ++++++---- src/testutils/dummyQuery.ts | 4 +- src/tokenTransferBuilders.ts | 4 +- 16 files changed, 77 insertions(+), 145 deletions(-) delete mode 100644 src/networkProviders/primitives.spec.ts delete mode 100644 src/networkProviders/primitives.ts diff --git a/src/networkProviders/accounts.ts b/src/networkProviders/accounts.ts index 7c2a29dc0..cee89aea6 100644 --- a/src/networkProviders/accounts.ts +++ b/src/networkProviders/accounts.ts @@ -1,12 +1,12 @@ import BigNumber from "bignumber.js"; +import { Address } from "../address"; import { IAddress } from "./interface"; -import { Address } from "./primitives"; /** * A plain view of an account, as queried from the Network. */ export class AccountOnNetwork { - address: IAddress = new Address(""); + address: IAddress = Address.empty(); nonce: number = 0; balance: BigNumber = new BigNumber(0); code: string = ""; @@ -65,7 +65,7 @@ export class GuardianData { class Guardian { activationEpoch: number = 0; - address: IAddress = new Address(""); + address: IAddress = Address.empty(); serviceUID: string = ""; static fromHttpResponse(responsePart: any): Guardian { diff --git a/src/networkProviders/apiNetworkProvider.ts b/src/networkProviders/apiNetworkProvider.ts index 0b777c35c..985a4b50e 100644 --- a/src/networkProviders/apiNetworkProvider.ts +++ b/src/networkProviders/apiNetworkProvider.ts @@ -1,5 +1,6 @@ import axios from "axios"; import { ErrContractQuery, ErrNetworkProvider } from "../errors"; +import { numberToPaddedHex } from "../utils.codec"; import { AccountOnNetwork, GuardianData } from "./accounts"; import { defaultAxiosConfig, defaultPagination } from "./config"; import { BaseUserAgent } from "./constants"; @@ -12,7 +13,6 @@ import { NetworkProviderConfig } from "./networkProviderConfig"; import { NetworkStake } from "./networkStake"; import { NetworkStatus } from "./networkStatus"; import { PairOnNetwork } from "./pairs"; -import { Nonce } from "./primitives"; import { ProxyNetworkProvider } from "./proxyNetworkProvider"; import { DefinitionOfFungibleTokenOnNetwork, DefinitionOfTokenCollectionOnNetwork } from "./tokenDefinitions"; import { FungibleTokenOfAccountOnNetwork, NonFungibleTokenOfAccountOnNetwork } from "./tokens"; @@ -115,7 +115,7 @@ export class ApiNetworkProvider implements INetworkProvider { collection: string, nonce: number, ): Promise { - const nonceAsHex = new Nonce(nonce).hex(); + const nonceAsHex = numberToPaddedHex(nonce); const response = await this.doGetGeneric(`accounts/${address.bech32()}/nfts/${collection}-${nonceAsHex}`); const tokenData = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response); return tokenData; @@ -181,7 +181,7 @@ export class ApiNetworkProvider implements INetworkProvider { } async getNonFungibleToken(collection: string, nonce: number): Promise { - const nonceAsHex = new Nonce(nonce).hex(); + const nonceAsHex = numberToPaddedHex(nonce); const response = await this.doGetGeneric(`nfts/${collection}-${nonceAsHex}`); const token = NonFungibleTokenOfAccountOnNetwork.fromApiHttpResponse(response); return token; diff --git a/src/networkProviders/constants.ts b/src/networkProviders/constants.ts index ea134f203..d85d175b4 100644 --- a/src/networkProviders/constants.ts +++ b/src/networkProviders/constants.ts @@ -1,7 +1,7 @@ import BigNumber from "bignumber.js"; -import { Address } from "./primitives"; +import { Address } from "../address"; export const MaxUint64AsBigNumber = new BigNumber("18446744073709551615"); export const EsdtContractAddress = new Address("erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u"); -export const BaseUserAgent = "multiversx-sdk" -export const UnknownClientName = "unknown" +export const BaseUserAgent = "multiversx-sdk"; +export const UnknownClientName = "unknown"; diff --git a/src/networkProviders/contractResults.ts b/src/networkProviders/contractResults.ts index 37046fb0d..a7f2a3674 100644 --- a/src/networkProviders/contractResults.ts +++ b/src/networkProviders/contractResults.ts @@ -1,6 +1,6 @@ +import { Address } from "../address"; import { IAddress } from "./interface"; import { TransactionLogs } from "./transactionLogs"; -import { Address } from "./primitives"; export class ContractResults { readonly items: ContractResultItem[]; @@ -14,12 +14,12 @@ export class ContractResults { } static fromProxyHttpResponse(results: any[]): ContractResults { - let items = results.map(item => ContractResultItem.fromProxyHttpResponse(item)); + let items = results.map((item) => ContractResultItem.fromProxyHttpResponse(item)); return new ContractResults(items); } static fromApiHttpResponse(results: any[]): ContractResults { - let items = results.map(item => ContractResultItem.fromApiHttpResponse(item)); + let items = results.map((item) => ContractResultItem.fromApiHttpResponse(item)); return new ContractResults(items); } } @@ -28,8 +28,8 @@ export class ContractResultItem { hash: string = ""; nonce: number = 0; value: string = ""; - receiver: IAddress = new Address(""); - sender: IAddress = new Address(""); + receiver: IAddress = Address.empty(); + sender: IAddress = Address.empty(); data: string = ""; previousHash: string = ""; originalHash: string = ""; diff --git a/src/networkProviders/pairs.ts b/src/networkProviders/pairs.ts index 723c25b7b..31a92bf12 100644 --- a/src/networkProviders/pairs.ts +++ b/src/networkProviders/pairs.ts @@ -1,9 +1,9 @@ -import {Address} from "./primitives"; -import {IAddress} from "./interface"; import BigNumber from "bignumber.js"; +import { Address } from "../address"; +import { IAddress } from "./interface"; export class PairOnNetwork { - address: IAddress = new Address(""); + address: IAddress = Address.empty(); id: string = ""; symbol: string = ""; name: string = ""; diff --git a/src/networkProviders/primitives.spec.ts b/src/networkProviders/primitives.spec.ts deleted file mode 100644 index 202a9dfee..000000000 --- a/src/networkProviders/primitives.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { assert } from "chai"; -import { Address } from "./primitives"; - -describe("test primitives", function () { - it("should create address from bech32 and from pubkey", async function () { - let aliceBech32 = "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"; - let bobBech32 = "erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"; - let alicePubkey = Buffer.from("0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", "hex"); - let bobPubkey = Buffer.from("8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8", "hex"); - - assert.equal(new Address(aliceBech32).bech32(), Address.fromPubkey(alicePubkey).bech32()); - assert.equal(new Address(bobBech32).bech32(), Address.fromPubkey(bobPubkey).bech32()); - assert.equal(new Address(aliceBech32).toString(), aliceBech32); - assert.equal(new Address(bobBech32).toString(), bobBech32); - }); -}); - diff --git a/src/networkProviders/primitives.ts b/src/networkProviders/primitives.ts deleted file mode 100644 index ed50b4efa..000000000 --- a/src/networkProviders/primitives.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as bech32 from "bech32"; -import { IAddress } from "./interface"; - -/** - * The human-readable-part of the bech32 addresses. - */ -const HRP = "erd"; - -export class Address implements IAddress { - private readonly value: string; - - constructor(value: string) { - this.value = value; - } - - static fromPubkey(pubkey: Buffer): IAddress { - let words = bech32.toWords(pubkey); - let address = bech32.encode(HRP, words); - return new Address(address); - } - - bech32(): string { - return this.value; - } - - toString() { - return this.bech32(); - } -} - -export class Nonce { - private readonly value: number; - - constructor(value: number) { - this.value = value; - } - - valueOf(): number { - return this.value; - } - - hex(): string { - return numberToPaddedHex(this.value); - } -} - -export function numberToPaddedHex(value: number) { - let hex = value.toString(16); - return zeroPadStringIfOddLength(hex); -} - -export function isPaddedHex(input: string) { - input = input || ""; - let decodedThenEncoded = Buffer.from(input, "hex").toString("hex"); - return input.toUpperCase() == decodedThenEncoded.toUpperCase(); -} - -export function zeroPadStringIfOddLength(input: string): string { - input = input || ""; - - if (input.length % 2 == 1) { - return "0" + input; - } - - return input; -} diff --git a/src/networkProviders/providers.dev.net.spec.ts b/src/networkProviders/providers.dev.net.spec.ts index 354cc67ae..d08e5b4f7 100644 --- a/src/networkProviders/providers.dev.net.spec.ts +++ b/src/networkProviders/providers.dev.net.spec.ts @@ -1,13 +1,13 @@ import { AxiosHeaders } from "axios"; import { assert } from "chai"; +import { Address } from "../address"; +import { MockQuery } from "../testutils/dummyQuery"; import { ApiNetworkProvider } from "./apiNetworkProvider"; import { INetworkProvider, ITransactionNext } from "./interface"; -import { Address } from "./primitives"; import { ProxyNetworkProvider } from "./proxyNetworkProvider"; import { NonFungibleTokenOfAccountOnNetwork } from "./tokens"; import { TransactionEventData } from "./transactionEvents"; import { TransactionOnNetwork } from "./transactions"; -import { MockQuery } from "../testutils/dummyQuery"; describe("test network providers on devnet: Proxy and API", function () { let alice = new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); diff --git a/src/networkProviders/tokenDefinitions.ts b/src/networkProviders/tokenDefinitions.ts index 63bfa523d..fbaccf2ac 100644 --- a/src/networkProviders/tokenDefinitions.ts +++ b/src/networkProviders/tokenDefinitions.ts @@ -1,12 +1,12 @@ import BigNumber from "bignumber.js"; +import { Address } from "../address"; import { IAddress } from "./interface"; -import { Address } from "./primitives"; export class DefinitionOfFungibleTokenOnNetwork { identifier: string = ""; name: string = ""; ticker: string = ""; - owner: IAddress = new Address(""); + owner: IAddress = Address.empty(); decimals: number = 0; supply: BigNumber = new BigNumber(0); isPaused: boolean = false; @@ -55,7 +55,7 @@ export class DefinitionOfFungibleTokenOnNetwork { result.identifier = identifier; result.name = tokenName.toString(); result.ticker = identifier; - result.owner = Address.fromPubkey(owner); + result.owner = new Address(owner); result.decimals = properties.NumDecimals.toNumber(); result.supply = new BigNumber(supply.toString()).shiftedBy(-result.decimals); result.isPaused = properties.IsPaused; @@ -76,7 +76,7 @@ export class DefinitionOfTokenCollectionOnNetwork { type: string = ""; name: string = ""; ticker: string = ""; - owner: IAddress = new Address(""); + owner: IAddress = Address.empty(); decimals: number = 0; canPause: boolean = false; canFreeze: boolean = false; @@ -120,7 +120,7 @@ export class DefinitionOfTokenCollectionOnNetwork { result.type = tokenType.toString(); result.name = tokenName.toString(); result.ticker = collection; - result.owner = Address.fromPubkey(owner); + result.owner = new Address(owner); result.decimals = properties.NumDecimals.toNumber() ?? 0; result.canPause = properties.CanPause || false; result.canFreeze = properties.CanFreeze || false; @@ -150,8 +150,11 @@ function parseTokenProperties(propertiesBuffers: Buffer[]): Record // This only handles booleans and numbers. function parseValueOfTokenProperty(value: string): any { switch (value) { - case "true": return true; - case "false": return false; - default: return new BigNumber(value); + case "true": + return true; + case "false": + return false; + default: + return new BigNumber(value); } } diff --git a/src/networkProviders/tokens.ts b/src/networkProviders/tokens.ts index 855d2428e..1c838f070 100644 --- a/src/networkProviders/tokens.ts +++ b/src/networkProviders/tokens.ts @@ -1,5 +1,6 @@ import { BigNumber } from "bignumber.js"; -import { Address, Nonce } from "./primitives"; +import { Address } from "../address"; +import { numberToPaddedHex } from "../utils.codec"; import { IAddress } from "./interface"; export class FungibleTokenOfAccountOnNetwork { @@ -26,7 +27,7 @@ export class NonFungibleTokenOfAccountOnNetwork { nonce: number = 0; type: string = ""; name: string = ""; - creator: IAddress = new Address(""); + creator: IAddress = Address.empty(); supply: BigNumber = new BigNumber(0); decimals: number = 0; royalties: BigNumber = new BigNumber(0); @@ -49,7 +50,7 @@ export class NonFungibleTokenOfAccountOnNetwork { static fromProxyHttpResponseByNonce(payload: any): NonFungibleTokenOfAccountOnNetwork { let result = NonFungibleTokenOfAccountOnNetwork.fromHttpResponse(payload); - let nonceAsHex = new Nonce(result.nonce).hex(); + let nonceAsHex = numberToPaddedHex(result.nonce); result.identifier = `${payload.tokenIdentifier}-${nonceAsHex}`; result.collection = payload.tokenIdentifier || ""; diff --git a/src/networkProviders/transactionEvents.ts b/src/networkProviders/transactionEvents.ts index b37d47abc..ab11c5892 100644 --- a/src/networkProviders/transactionEvents.ts +++ b/src/networkProviders/transactionEvents.ts @@ -1,8 +1,8 @@ +import { Address } from "../address"; import { IAddress } from "./interface"; -import { Address } from "./primitives"; export class TransactionEvent { - address: IAddress = new Address(""); + address: IAddress = Address.empty(); identifier: string = ""; topics: TransactionEventTopic[] = []; @@ -18,16 +18,16 @@ export class TransactionEvent { } static fromHttpResponse(responsePart: { - address: string, - identifier: string, - topics: string[], - data: string, - additionalData?: string[] + address: string; + identifier: string; + topics: string[]; + data: string; + additionalData?: string[]; }): TransactionEvent { let result = new TransactionEvent(); result.address = new Address(responsePart.address); result.identifier = responsePart.identifier || ""; - result.topics = (responsePart.topics || []).map(topic => new TransactionEventTopic(topic)); + result.topics = (responsePart.topics || []).map((topic) => new TransactionEventTopic(topic)); result.dataPayload = TransactionEventData.fromBase64(responsePart.data); result.additionalData = (responsePart.additionalData || []).map(TransactionEventData.fromBase64); @@ -37,7 +37,7 @@ export class TransactionEvent { } findFirstOrNoneTopic(predicate: (topic: TransactionEventTopic) => boolean): TransactionEventTopic | undefined { - return this.topics.filter(topic => predicate(topic))[0]; + return this.topics.filter((topic) => predicate(topic))[0]; } getLastTopic(): TransactionEventTopic { diff --git a/src/networkProviders/transactionLogs.ts b/src/networkProviders/transactionLogs.ts index 36f5d021d..36c10690d 100644 --- a/src/networkProviders/transactionLogs.ts +++ b/src/networkProviders/transactionLogs.ts @@ -1,10 +1,10 @@ +import { Address } from "../address"; import { ErrUnexpectedCondition } from "./../errors"; import { IAddress } from "./interface"; -import { Address } from "./primitives"; import { TransactionEvent } from "./transactionEvents"; export class TransactionLogs { - address: IAddress = new Address(""); + address: IAddress = Address.empty(); events: TransactionEvent[] = []; constructor(init?: Partial) { diff --git a/src/networkProviders/transactionReceipt.ts b/src/networkProviders/transactionReceipt.ts index 07e72fb56..49d14d62e 100644 --- a/src/networkProviders/transactionReceipt.ts +++ b/src/networkProviders/transactionReceipt.ts @@ -1,17 +1,17 @@ +import { Address } from "../address"; import { IAddress } from "./interface"; -import { Address } from "./primitives"; export class TransactionReceipt { value: string = ""; - sender: IAddress = new Address(""); + sender: IAddress = Address.empty(); data: string = ""; hash: string = ""; static fromHttpResponse(response: { - value: string, - sender: string, - data: string, - txHash: string + value: string; + sender: string; + data: string; + txHash: string; }): TransactionReceipt { let receipt = new TransactionReceipt(); diff --git a/src/networkProviders/transactions.ts b/src/networkProviders/transactions.ts index beaecc0a8..232691315 100644 --- a/src/networkProviders/transactions.ts +++ b/src/networkProviders/transactions.ts @@ -1,12 +1,12 @@ -import { TransactionStatus } from "./transactionStatus"; +import { Address } from "../address"; import { ContractResults } from "./contractResults"; -import { Address } from "./primitives"; import { IAddress, ITransaction, ITransactionNext } from "./interface"; import { TransactionLogs } from "./transactionLogs"; import { TransactionReceipt } from "./transactionReceipt"; +import { TransactionStatus } from "./transactionStatus"; export function prepareTransactionForBroadcasting(transaction: ITransaction | ITransactionNext): any { - if ("toSendable" in transaction){ + if ("toSendable" in transaction) { return transaction.toSendable(); } @@ -15,8 +15,12 @@ export function prepareTransactionForBroadcasting(transaction: ITransaction | IT value: transaction.value.toString(), receiver: transaction.receiver, sender: transaction.sender, - senderUsername: transaction.senderUsername ? Buffer.from(transaction.senderUsername).toString("base64") : undefined, - receiverUsername: transaction.receiverUsername ? Buffer.from(transaction.receiverUsername).toString("base64") : undefined, + senderUsername: transaction.senderUsername + ? Buffer.from(transaction.senderUsername).toString("base64") + : undefined, + receiverUsername: transaction.receiverUsername + ? Buffer.from(transaction.receiverUsername).toString("base64") + : undefined, gasPrice: Number(transaction.gasPrice), gasLimit: Number(transaction.gasLimit), data: transaction.data.length === 0 ? undefined : Buffer.from(transaction.data).toString("base64"), @@ -25,8 +29,11 @@ export function prepareTransactionForBroadcasting(transaction: ITransaction | IT options: transaction.options, guardian: transaction.guardian || undefined, signature: Buffer.from(transaction.signature).toString("hex"), - guardianSignature: transaction.guardianSignature.length === 0 ? undefined : Buffer.from(transaction.guardianSignature).toString("hex"), - } + guardianSignature: + transaction.guardianSignature.length === 0 + ? undefined + : Buffer.from(transaction.guardianSignature).toString("hex"), + }; } export class TransactionOnNetwork { @@ -37,8 +44,8 @@ export class TransactionOnNetwork { round: number = 0; epoch: number = 0; value: string = ""; - receiver: IAddress = new Address(""); - sender: IAddress = new Address(""); + receiver: IAddress = Address.empty(); + sender: IAddress = Address.empty(); gasLimit: number = 0; gasPrice: number = 0; function: string = ""; @@ -59,13 +66,17 @@ export class TransactionOnNetwork { Object.assign(this, init); } - static fromProxyHttpResponse(txHash: string, response: any, processStatus?: TransactionStatus | undefined): TransactionOnNetwork { + static fromProxyHttpResponse( + txHash: string, + response: any, + processStatus?: TransactionStatus | undefined, + ): TransactionOnNetwork { let result = TransactionOnNetwork.fromHttpResponse(txHash, response); result.contractResults = ContractResults.fromProxyHttpResponse(response.smartContractResults || []); if (processStatus) { result.status = processStatus; - result.isCompleted = result.status.isSuccessful() || result.status.isFailed() + result.isCompleted = result.status.isSuccessful() || result.status.isFailed(); } return result; diff --git a/src/testutils/dummyQuery.ts b/src/testutils/dummyQuery.ts index 525eeaa0c..076369df3 100644 --- a/src/testutils/dummyQuery.ts +++ b/src/testutils/dummyQuery.ts @@ -3,8 +3,8 @@ import { IAddress } from "../interface"; import { IContractQuery } from "../networkProviders/interface"; export class MockQuery implements IContractQuery { - caller: IAddress = new Address(""); - address: IAddress = new Address(""); + caller: IAddress = Address.empty(); + address: IAddress = Address.empty(); func: string = ""; args: string[] = []; value: string = ""; diff --git a/src/tokenTransferBuilders.ts b/src/tokenTransferBuilders.ts index 7760ca7d9..a557ca363 100644 --- a/src/tokenTransferBuilders.ts +++ b/src/tokenTransferBuilders.ts @@ -35,7 +35,7 @@ export class ESDTTransferPayloadBuilder { */ export class ESDTNFTTransferPayloadBuilder { payment: ITokenTransfer = TokenTransfer.nonFungible("", 0); - destination: IAddress = new Address(""); + destination: IAddress = Address.empty(); setPayment(payment: ITokenTransfer): ESDTNFTTransferPayloadBuilder { this.payment = payment; @@ -70,7 +70,7 @@ export class ESDTNFTTransferPayloadBuilder { */ export class MultiESDTNFTTransferPayloadBuilder { payments: ITokenTransfer[] = []; - destination: IAddress = new Address(""); + destination: IAddress = Address.empty(); setPayments(payments: ITokenTransfer[]): MultiESDTNFTTransferPayloadBuilder { this.payments = payments; From 996a8e5d4e3b01aa909bae1482625dc33ec8118a Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 14 Oct 2024 14:38:16 +0300 Subject: [PATCH 105/118] Make axios and bls optional --- package-lock.json | 46 ++++++++++++++++++++----------------- package.json | 8 ++++--- src/wallet/validatorKeys.ts | 1 + 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26f31f194..1d439e3cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "13.7.0", "license": "MIT", "dependencies": { - "@multiversx/sdk-bls-wasm": "0.3.5", "@multiversx/sdk-transaction-decoder": "1.0.2", "@noble/ed25519": "1.7.3", "@noble/hashes": "1.3.0", @@ -46,8 +45,11 @@ "ts-node": "9.1.1", "typescript": "4.1.2" }, + "optionalDependencies": { + "@multiversx/sdk-bls-wasm": "0.3.5", + "axios": "^1.7.4" + }, "peerDependencies": { - "axios": "^1.7.4", "bignumber.js": "^9.0.1", "protobufjs": "^7.2.6" } @@ -160,6 +162,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@multiversx/sdk-bls-wasm/-/sdk-bls-wasm-0.3.5.tgz", "integrity": "sha512-c0tIdQUnbBLSt6NYU+OpeGPYdL0+GV547HeHT8Xc0BKQ7Cj0v82QUoA2QRtWrR1G4MNZmLsIacZSsf6DrIS2Bw==", + "optional": true, "engines": { "node": ">=8.9.0" } @@ -868,7 +871,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "peer": true + "optional": true }, "node_modules/available-typed-arrays": { "version": "1.0.5", @@ -886,7 +889,7 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", - "peer": true, + "optional": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1406,7 +1409,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "peer": true, + "optional": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1624,7 +1627,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "peer": true, + "optional": true, "engines": { "node": ">=0.4.0" } @@ -2402,7 +2405,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "peer": true, + "optional": true, "engines": { "node": ">=4.0" }, @@ -2425,7 +2428,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "peer": true, + "optional": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3432,7 +3435,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true, + "optional": true, "engines": { "node": ">= 0.6" } @@ -3441,7 +3444,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, + "optional": true, "dependencies": { "mime-db": "1.52.0" }, @@ -4195,7 +4198,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "peer": true + "optional": true }, "node_modules/public-encrypt": { "version": "4.0.3", @@ -5281,7 +5284,8 @@ "@multiversx/sdk-bls-wasm": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@multiversx/sdk-bls-wasm/-/sdk-bls-wasm-0.3.5.tgz", - "integrity": "sha512-c0tIdQUnbBLSt6NYU+OpeGPYdL0+GV547HeHT8Xc0BKQ7Cj0v82QUoA2QRtWrR1G4MNZmLsIacZSsf6DrIS2Bw==" + "integrity": "sha512-c0tIdQUnbBLSt6NYU+OpeGPYdL0+GV547HeHT8Xc0BKQ7Cj0v82QUoA2QRtWrR1G4MNZmLsIacZSsf6DrIS2Bw==", + "optional": true }, "@multiversx/sdk-transaction-decoder": { "version": "1.0.2", @@ -5814,7 +5818,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "peer": true + "optional": true }, "available-typed-arrays": { "version": "1.0.5", @@ -5826,7 +5830,7 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", - "peer": true, + "optional": true, "requires": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -6268,7 +6272,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "peer": true, + "optional": true, "requires": { "delayed-stream": "~1.0.0" } @@ -6450,7 +6454,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "peer": true + "optional": true }, "deps-sort": { "version": "2.0.1", @@ -7045,7 +7049,7 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "peer": true + "optional": true }, "for-each": { "version": "0.3.3", @@ -7060,7 +7064,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "peer": true, + "optional": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7823,13 +7827,13 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true + "optional": true }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, + "optional": true, "requires": { "mime-db": "1.52.0" } @@ -8391,7 +8395,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "peer": true + "optional": true }, "public-encrypt": { "version": "4.0.3", diff --git a/package.json b/package.json index 4a5358470..0ea7fe47a 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ }, "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", - "@multiversx/sdk-bls-wasm": "0.3.5", "json-bigint": "1.0.0", "bech32": "1.1.4", "bip39": "3.1.0", @@ -75,7 +74,10 @@ }, "peerDependencies": { "bignumber.js": "^9.0.1", - "protobufjs": "^7.2.6", - "axios": "^1.7.4" + "protobufjs": "^7.2.6" + }, + "optionalDependencies": { + "axios": "^1.7.4", + "@multiversx/sdk-bls-wasm": "0.3.5" } } diff --git a/src/wallet/validatorKeys.ts b/src/wallet/validatorKeys.ts index d91bc315a..3a7928fad 100644 --- a/src/wallet/validatorKeys.ts +++ b/src/wallet/validatorKeys.ts @@ -2,6 +2,7 @@ import { ErrInvariantFailed } from "../errors"; import { guardLength } from "./assertions"; import { parseValidatorKey } from "./pem"; +// eslint-disable-next-line @typescript-eslint/no-var-requires const bls = require("@multiversx/sdk-bls-wasm"); export const VALIDATOR_SECRETKEY_LENGTH = 32; From 6f260e4054a09c8fdd67b032815a540da31059af Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 14 Oct 2024 16:11:13 +0300 Subject: [PATCH 106/118] Add axios and bls handling in case not install --- README.md | 18 ++++++++++++++++++ src/networkProviders/apiNetworkProvider.ts | 9 ++++++--- src/networkProviders/proxyNetworkProvider.ts | 8 +++++--- src/testutils/utils.ts | 16 +++++++++------- src/testutils/wallets.ts | 5 +++-- src/utils.ts | 10 ++++++++++ src/wallet/validatorKeys.ts | 20 ++++++++++++++------ 7 files changed, 65 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d27116ece..a0212ab04 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,24 @@ npm install --global browserify npm install esmify --no-save ``` +## Optional Dependencies + +### axios + +This package can make HTTP requests using `axios`, but it is not bundled by default. If you plan to use the API network provider or Proxy network provider, make sure to install `axios`: + +```bash +npm install axios +``` + +### @multiversx/sdk-bls-wasm + +This package requires `@multiversx/sdk-bls-wasm` for BLS (Boneh-Lynn-Shacham) cryptographic functions, but it is not bundled by default. If you plan to use BLS functionality, make sure to install this optional dependency: + +```bash +npm install @multiversx/sdk-bls-wasm +``` + ### Building the library In order to compile the library, run the following: diff --git a/src/networkProviders/apiNetworkProvider.ts b/src/networkProviders/apiNetworkProvider.ts index 985a4b50e..eae0bc976 100644 --- a/src/networkProviders/apiNetworkProvider.ts +++ b/src/networkProviders/apiNetworkProvider.ts @@ -1,5 +1,5 @@ -import axios from "axios"; import { ErrContractQuery, ErrNetworkProvider } from "../errors"; +import { getAxios } from "../utils"; import { numberToPaddedHex } from "../utils.codec"; import { AccountOnNetwork, GuardianData } from "./accounts"; import { defaultAxiosConfig, defaultPagination } from "./config"; @@ -202,10 +202,12 @@ export class ApiNetworkProvider implements INetworkProvider { } private async doGet(resourceUrl: string): Promise { + let axios = await getAxios(); + const url = `${this.url}/${resourceUrl}`; try { - const response = await axios.get(url, this.config); + const response = await axios.default.get(url, this.config); return response.data; } catch (error) { this.handleApiError(error, resourceUrl); @@ -213,10 +215,11 @@ export class ApiNetworkProvider implements INetworkProvider { } private async doPost(resourceUrl: string, payload: any): Promise { + let axios = await getAxios(); const url = `${this.url}/${resourceUrl}`; try { - const response = await axios.post(url, payload, { + const response = await axios.default.post(url, payload, { ...this.config, headers: { "Content-Type": "application/json", diff --git a/src/networkProviders/proxyNetworkProvider.ts b/src/networkProviders/proxyNetworkProvider.ts index 4a70cf14b..4fd933841 100644 --- a/src/networkProviders/proxyNetworkProvider.ts +++ b/src/networkProviders/proxyNetworkProvider.ts @@ -1,5 +1,5 @@ -import axios from "axios"; import { ErrContractQuery, ErrNetworkProvider } from "../errors"; +import { getAxios } from "../utils"; import { AccountOnNetwork, GuardianData } from "./accounts"; import { defaultAxiosConfig } from "./config"; import { BaseUserAgent, EsdtContractAddress } from "./constants"; @@ -224,10 +224,11 @@ export class ProxyNetworkProvider implements INetworkProvider { } private async doGet(resourceUrl: string): Promise { + const axios = await getAxios(); const url = `${this.url}/${resourceUrl}`; try { - const response = await axios.get(url, this.config); + const response = await axios.default.get(url, this.config); const payload = response.data.data; return payload; } catch (error) { @@ -236,10 +237,11 @@ export class ProxyNetworkProvider implements INetworkProvider { } private async doPost(resourceUrl: string, payload: any): Promise { + const axios = await getAxios(); const url = `${this.url}/${resourceUrl}`; try { - const response = await axios.post(url, payload, { + const response = await axios.default.post(url, payload, { ...this.config, headers: { "Content-Type": "application/json", diff --git a/src/testutils/utils.ts b/src/testutils/utils.ts index 76ef1b9f8..349e82f3b 100644 --- a/src/testutils/utils.ts +++ b/src/testutils/utils.ts @@ -1,14 +1,14 @@ -import { PathLike } from "fs"; +import BigNumber from "bignumber.js"; import * as fs from "fs"; -import { SmartContract } from "../smartcontracts/smartContract"; +import { PathLike } from "fs"; +import { IChainID, IGasLimit } from "../interface"; import { Code } from "../smartcontracts/code"; +import { SmartContract } from "../smartcontracts/smartContract"; import { AbiRegistry, TypedValue } from "../smartcontracts/typesystem"; import { Transaction } from "../transaction"; import { TransactionWatcher } from "../transactionWatcher"; -import { IChainID, IGasLimit } from "../interface"; +import { getAxios } from "../utils"; import { TestWallet } from "./wallets"; -import axios, { AxiosResponse } from "axios"; -import BigNumber from "bignumber.js"; export async function prepareDeployment(obj: { deployer: TestWallet; @@ -41,7 +41,8 @@ export async function prepareDeployment(obj: { export async function loadContractCode(path: PathLike): Promise { if (isOnBrowserTests()) { - let response: AxiosResponse = await axios.get(path.toString(), { + const axios = await getAxios(); + let response: any = await axios.default.get(path.toString(), { responseType: "arraybuffer", transformResponse: [], headers: { @@ -60,7 +61,8 @@ export async function loadContractCode(path: PathLike): Promise { export async function loadAbiRegistry(path: PathLike): Promise { if (isOnBrowserTests()) { - let response: AxiosResponse = await axios.get(path.toString()); + const axios = await getAxios(); + let response: any = await axios.default.get(path.toString()); return AbiRegistry.create(response.data); } diff --git a/src/testutils/wallets.ts b/src/testutils/wallets.ts index 9c5add288..e332cec0c 100644 --- a/src/testutils/wallets.ts +++ b/src/testutils/wallets.ts @@ -1,10 +1,10 @@ -import axios from "axios"; import * as fs from "fs"; import * as path from "path"; import { Account } from "../account"; import { Address } from "../address"; import { IAddress } from "../interface"; import { IAccountOnNetwork } from "../interfaceOfNetwork"; +import { getAxios } from "../utils"; import { UserSecretKey, UserSigner } from "./../wallet"; import { readTestFile } from "./files"; import { isOnBrowserTests } from "./utils"; @@ -82,7 +82,8 @@ async function readTestWalletFileContents(name: string): Promise { } async function downloadTextFile(url: string) { - let response = await axios.get(url, { responseType: "text", transformResponse: [] }); + const axios = await getAxios(); + let response = await axios.default.get(url, { responseType: "text", transformResponse: [] }); let text = response.data.toString(); return text; } diff --git a/src/utils.ts b/src/utils.ts index dd72af659..9cc935224 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -56,3 +56,13 @@ export function isEmpty(value: { isEmpty?: () => boolean; length?: number }): bo return value.length === 0; } + +export async function getAxios() { + let axios; + try { + axios = require("axios"); + return axios; + } catch (error) { + throw new Error("axios is required but not installed. Please install axios to make network requests."); + } +} diff --git a/src/wallet/validatorKeys.ts b/src/wallet/validatorKeys.ts index 3a7928fad..4b2b856f7 100644 --- a/src/wallet/validatorKeys.ts +++ b/src/wallet/validatorKeys.ts @@ -2,21 +2,29 @@ import { ErrInvariantFailed } from "../errors"; import { guardLength } from "./assertions"; import { parseValidatorKey } from "./pem"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const bls = require("@multiversx/sdk-bls-wasm"); - export const VALIDATOR_SECRETKEY_LENGTH = 32; export const VALIDATOR_PUBKEY_LENGTH = 96; export class BLS { private static isInitialized: boolean = false; + public static bls: any; + + private static loadBLSModule() { + if (!BLS.bls) { + try { + BLS.bls = require("@multiversx/sdk-bls-wasm"); + } catch (error) { + throw new Error("BLS module is required but not installed. Please install '@multiversx/sdk-bls-wasm'."); + } + } + } static async initIfNecessary() { if (BLS.isInitialized) { return; } - - await bls.init(bls.BLS12_381); + BLS.loadBLSModule(); + await BLS.bls.init(BLS.bls.BLS12_381); BLS.isInitialized = true; } @@ -38,7 +46,7 @@ export class ValidatorSecretKey { BLS.guardInitialized(); guardLength(buffer, VALIDATOR_SECRETKEY_LENGTH); - this.secretKey = new bls.SecretKey(); + this.secretKey = new BLS.bls.SecretKey(); this.secretKey.setLittleEndian(Uint8Array.from(buffer)); this.publicKey = this.secretKey.getPublicKey(); } From df8d1adcf3ee3957ff98c341bf7f1b583b2567a2 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Oct 2024 12:09:59 +0300 Subject: [PATCH 107/118] Update readme and move axios to constructor --- README.md | 4 ++-- src/networkProviders/apiNetworkProvider.ts | 9 ++++----- src/networkProviders/proxyNetworkProvider.ts | 8 ++++---- src/utils.ts | 6 ++---- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a0212ab04..3ed95086c 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ npm install esmify --no-save ### axios -This package can make HTTP requests using `axios`, but it is not bundled by default. If you plan to use the API network provider or Proxy network provider, make sure to install `axios`: +This package can make HTTP requests using `axios`, which is not bundled by default. If you plan to use the API network provider or Proxy network provider, make sure to install `axios`: ```bash npm install axios @@ -44,7 +44,7 @@ npm install axios ### @multiversx/sdk-bls-wasm -This package requires `@multiversx/sdk-bls-wasm` for BLS (Boneh-Lynn-Shacham) cryptographic functions, but it is not bundled by default. If you plan to use BLS functionality, make sure to install this optional dependency: +This package requires `@multiversx/sdk-bls-wasm` for BLS (Boneh-Lynn-Shacham) cryptographic functions, which is not bundled by default. If you plan to use BLS functionality, make sure to install this optional dependency: ```bash npm install @multiversx/sdk-bls-wasm diff --git a/src/networkProviders/apiNetworkProvider.ts b/src/networkProviders/apiNetworkProvider.ts index eae0bc976..177f3ab3d 100644 --- a/src/networkProviders/apiNetworkProvider.ts +++ b/src/networkProviders/apiNetworkProvider.ts @@ -26,12 +26,14 @@ export class ApiNetworkProvider implements INetworkProvider { private config: NetworkProviderConfig; private backingProxyNetworkProvider; private userAgentPrefix = `${BaseUserAgent}/api`; + private axios: any; constructor(url: string, config?: NetworkProviderConfig) { this.url = url; const proxyConfig = this.getProxyConfig(config); this.config = { ...defaultAxiosConfig, ...config }; this.backingProxyNetworkProvider = new ProxyNetworkProvider(url, proxyConfig); + this.axios = getAxios(); extendUserAgent(this.userAgentPrefix, this.config); } @@ -202,12 +204,10 @@ export class ApiNetworkProvider implements INetworkProvider { } private async doGet(resourceUrl: string): Promise { - let axios = await getAxios(); - const url = `${this.url}/${resourceUrl}`; try { - const response = await axios.default.get(url, this.config); + const response = await this.axios.default.get(url, this.config); return response.data; } catch (error) { this.handleApiError(error, resourceUrl); @@ -215,11 +215,10 @@ export class ApiNetworkProvider implements INetworkProvider { } private async doPost(resourceUrl: string, payload: any): Promise { - let axios = await getAxios(); const url = `${this.url}/${resourceUrl}`; try { - const response = await axios.default.post(url, payload, { + const response = await this.axios.default.post(url, payload, { ...this.config, headers: { "Content-Type": "application/json", diff --git a/src/networkProviders/proxyNetworkProvider.ts b/src/networkProviders/proxyNetworkProvider.ts index 4fd933841..e62327ea4 100644 --- a/src/networkProviders/proxyNetworkProvider.ts +++ b/src/networkProviders/proxyNetworkProvider.ts @@ -22,10 +22,12 @@ export class ProxyNetworkProvider implements INetworkProvider { private url: string; private config: NetworkProviderConfig; private userAgentPrefix = `${BaseUserAgent}/proxy`; + private axios: any; constructor(url: string, config?: NetworkProviderConfig) { this.url = url; this.config = { ...defaultAxiosConfig, ...config }; + this.axios = getAxios(); extendUserAgent(this.userAgentPrefix, this.config); } @@ -224,11 +226,10 @@ export class ProxyNetworkProvider implements INetworkProvider { } private async doGet(resourceUrl: string): Promise { - const axios = await getAxios(); const url = `${this.url}/${resourceUrl}`; try { - const response = await axios.default.get(url, this.config); + const response = await this.axios.default.get(url, this.config); const payload = response.data.data; return payload; } catch (error) { @@ -237,11 +238,10 @@ export class ProxyNetworkProvider implements INetworkProvider { } private async doPost(resourceUrl: string, payload: any): Promise { - const axios = await getAxios(); const url = `${this.url}/${resourceUrl}`; try { - const response = await axios.default.post(url, payload, { + const response = await this.axios.default.post(url, payload, { ...this.config, headers: { "Content-Type": "application/json", diff --git a/src/utils.ts b/src/utils.ts index 9cc935224..c80c7c6df 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -57,11 +57,9 @@ export function isEmpty(value: { isEmpty?: () => boolean; length?: number }): bo return value.length === 0; } -export async function getAxios() { - let axios; +export function getAxios() { try { - axios = require("axios"); - return axios; + return require("axios"); } catch (error) { throw new Error("axios is required but not installed. Please install axios to make network requests."); } From 1c9e32618e9e441271c1d03286bc44d994543970 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Oct 2024 15:09:30 +0300 Subject: [PATCH 108/118] Mark ITransactionNext as deprecated --- src/networkProviders/interface.ts | 45 +++++++++---------- .../providers.dev.net.spec.ts | 2 + 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/networkProviders/interface.ts b/src/networkProviders/interface.ts index 144f05834..501ab4a59 100644 --- a/src/networkProviders/interface.ts +++ b/src/networkProviders/interface.ts @@ -1,3 +1,4 @@ +import { ITransaction as ITransactionAsInSpecs } from "../interface"; import { AccountOnNetwork } from "./accounts"; import { ContractQueryResponse } from "./contractQueryResponse"; import { NetworkConfig } from "./networkConfig"; @@ -46,7 +47,10 @@ export interface INetworkProvider { /** * Fetches data about the non-fungible tokens held by account. */ - getNonFungibleTokensOfAccount(address: IAddress, pagination?: IPagination): Promise; + getNonFungibleTokensOfAccount( + address: IAddress, + pagination?: IPagination, + ): Promise; /** * Fetches data about a specific fungible token held by an account. @@ -56,7 +60,11 @@ export interface INetworkProvider { /** * Fetches data about a specific non-fungible token (instance) held by an account. */ - getNonFungibleTokenOfAccount(address: IAddress, collection: string, nonce: number): Promise; + getNonFungibleTokenOfAccount( + address: IAddress, + collection: string, + nonce: number, + ): Promise; /** * Fetches the state of a transaction. @@ -80,7 +88,7 @@ export interface INetworkProvider { /** * Simulates the processing of an already-signed transaction. - * + * */ simulateTransaction(tx: ITransaction): Promise; @@ -118,8 +126,8 @@ export interface INetworkProvider { export interface IContractQuery { address: IAddress; caller?: IAddress; - func: { toString(): string; }; - value?: { toString(): string; }; + func: { toString(): string }; + value?: { toString(): string }; getEncodedArguments(): string[]; } @@ -132,22 +140,11 @@ export interface ITransaction { toSendable(): any; } -export interface IAddress { bech32(): string; } - -export interface ITransactionNext { - sender: string; - receiver: string; - gasLimit: bigint; - chainID: string; - nonce: bigint; - value: bigint; - senderUsername: string; - receiverUsername: string; - gasPrice: bigint; - data: Uint8Array; - version: number; - options: number; - guardian: string; - signature: Uint8Array; - guardianSignature: Uint8Array; - } +export interface IAddress { + bech32(): string; +} + +/** + * @deprecated This will be remove with the next release (replaced by the `ITransaction` interface from "src/interface.ts"). + */ +export type ITransactionNext = ITransactionAsInSpecs; diff --git a/src/networkProviders/providers.dev.net.spec.ts b/src/networkProviders/providers.dev.net.spec.ts index d08e5b4f7..b95994cb8 100644 --- a/src/networkProviders/providers.dev.net.spec.ts +++ b/src/networkProviders/providers.dev.net.spec.ts @@ -452,6 +452,8 @@ describe("test network providers on devnet: Proxy and API", function () { guardian: "", guardianSignature: new Uint8Array(), options: 0, + relayer: "", + innerTransactions: [], }; const apiLegacyTxHash = await apiProvider.sendTransaction(transaction); From 6ceb8be859d06cb3bc296682a7e19df125782f44 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Oct 2024 15:36:44 +0300 Subject: [PATCH 109/118] Fix typo --- src/networkProviders/interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/networkProviders/interface.ts b/src/networkProviders/interface.ts index 501ab4a59..eb73db286 100644 --- a/src/networkProviders/interface.ts +++ b/src/networkProviders/interface.ts @@ -145,6 +145,6 @@ export interface IAddress { } /** - * @deprecated This will be remove with the next release (replaced by the `ITransaction` interface from "src/interface.ts"). + * @deprecated This will be removed with the next release (replaced by the `ITransaction` interface from "src/interface.ts"). */ export type ITransactionNext = ITransactionAsInSpecs; From e519e5ef32fa6e4838af235adffe5b6d87ee51d9 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Oct 2024 16:02:18 +0300 Subject: [PATCH 110/118] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e392af4e..86b115dcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.9.0", + "version": "13.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.9.0", + "version": "13.10.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index fb05d43ce..ec8e434f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.9.0", + "version": "13.10.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From d44485a5f14792f15c3d2f625bdd56f4e60ce60b Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 15 Oct 2024 16:15:02 +0300 Subject: [PATCH 111/118] fix version bump for alpha release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86b115dcf..af4bb675e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.10.0", + "version": "13.10.0-alpha.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.10.0", + "version": "13.10.0-alpha.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index ec8e434f4..6561ba53a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.10.0", + "version": "13.10.0-alpha.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From bb10ad7249187724ba281421cf39f292056ce436 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 16 Oct 2024 14:26:21 +0300 Subject: [PATCH 112/118] Update version for alpha release --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index af4bb675e..86b115dcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.10.0-alpha.0", + "version": "13.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.10.0-alpha.0", + "version": "13.10.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 6561ba53a..ec8e434f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.10.0-alpha.0", + "version": "13.10.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From 47145abfed3968235f7aef663c8305067d4c6c24 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 21 Oct 2024 15:16:17 +0300 Subject: [PATCH 113/118] Make bip39 optional --- README.md | 8 ++++++++ package-lock.json | 10 ++++++---- package.json | 6 +++--- src/wallet/mnemonic.ts | 29 +++++++++++++++++++++++------ 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 3ed95086c..fbb658797 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,14 @@ This package requires `@multiversx/sdk-bls-wasm` for BLS (Boneh-Lynn-Shacham) cr npm install @multiversx/sdk-bls-wasm ``` +### bip39 + +This package provides mnemonic and seed generation functionality using `bip39`, but it is not bundled by default. If you plan to use mnemonic-related features, make sure to install this optional dependency: + +```bash +npm install bip39 +``` + ### Building the library In order to compile the library, run the following: diff --git a/package-lock.json b/package-lock.json index 86b115dcf..97c9d84a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,18 @@ { "name": "@multiversx/sdk-core", - "version": "13.10.0", + "version": "13.11.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.10.0", + "version": "13.11.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", "@noble/ed25519": "1.7.3", "@noble/hashes": "1.3.0", "bech32": "1.1.4", - "bip39": "3.1.0", "blake2b": "2.1.3", "buffer": "6.0.3", "ed25519-hd-key": "1.1.2", @@ -47,7 +46,8 @@ }, "optionalDependencies": { "@multiversx/sdk-bls-wasm": "0.3.5", - "axios": "^1.7.4" + "axios": "^1.7.4", + "bip39": "3.1.0" }, "peerDependencies": { "bignumber.js": "^9.0.1", @@ -947,6 +947,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "optional": true, "dependencies": { "@noble/hashes": "^1.2.0" } @@ -5868,6 +5869,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "optional": true, "requires": { "@noble/hashes": "^1.2.0" } diff --git a/package.json b/package.json index ec8e434f4..d64a6d55e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.10.0", + "version": "13.11.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", @@ -40,7 +40,6 @@ "@multiversx/sdk-transaction-decoder": "1.0.2", "json-bigint": "1.0.0", "bech32": "1.1.4", - "bip39": "3.1.0", "blake2b": "2.1.3", "buffer": "6.0.3", "ed25519-hd-key": "1.1.2", @@ -79,6 +78,7 @@ }, "optionalDependencies": { "axios": "^1.7.4", - "@multiversx/sdk-bls-wasm": "0.3.5" + "@multiversx/sdk-bls-wasm": "0.3.5", + "bip39": "3.1.0" } } diff --git a/src/wallet/mnemonic.ts b/src/wallet/mnemonic.ts index 8e4b2baa5..7d2024814 100644 --- a/src/wallet/mnemonic.ts +++ b/src/wallet/mnemonic.ts @@ -1,4 +1,3 @@ -import { entropyToMnemonic, generateMnemonic, mnemonicToEntropy, mnemonicToSeedSync, validateMnemonic } from "bip39"; import { derivePath } from "ed25519-hd-key"; import { ErrBadMnemonicEntropy, ErrWrongMnemonic } from "../errors"; import { UserSecretKey } from "./userKeys"; @@ -6,6 +5,18 @@ import { UserSecretKey } from "./userKeys"; const MNEMONIC_STRENGTH = 256; const BIP44_DERIVATION_PREFIX = "m/44'/508'/0'/0'"; +let bip39: any; + +function loadBip39() { + if (!bip39) { + try { + bip39 = require("bip39"); + } catch (error) { + throw new Error("bip39 is required but not installed. Please install 'bip39' to use mnemonic features."); + } + } +} + export class Mnemonic { private readonly text: string; @@ -14,11 +25,13 @@ export class Mnemonic { } static generate(): Mnemonic { - const text = generateMnemonic(MNEMONIC_STRENGTH); + loadBip39(); // Load bip39 when needed + const text = bip39.generateMnemonic(MNEMONIC_STRENGTH); return new Mnemonic(text); } static fromString(text: string) { + loadBip39(); // Load bip39 when needed text = text.trim(); Mnemonic.assertTextIsValid(text); @@ -26,8 +39,9 @@ export class Mnemonic { } static fromEntropy(entropy: Uint8Array): Mnemonic { + loadBip39(); // Load bip39 when needed try { - const text = entropyToMnemonic(Buffer.from(entropy)); + const text = bip39.entropyToMnemonic(Buffer.from(entropy)); return new Mnemonic(text); } catch (err: any) { throw new ErrBadMnemonicEntropy(err); @@ -35,7 +49,8 @@ export class Mnemonic { } public static assertTextIsValid(text: string) { - let isValid = validateMnemonic(text); + loadBip39(); // Load bip39 when needed + let isValid = bip39.validateMnemonic(text); if (!isValid) { throw new ErrWrongMnemonic(); @@ -43,7 +58,8 @@ export class Mnemonic { } deriveKey(addressIndex: number = 0, password: string = ""): UserSecretKey { - let seed = mnemonicToSeedSync(this.text, password); + loadBip39(); // Load bip39 when needed + let seed = bip39.mnemonicToSeedSync(this.text, password); let derivationPath = `${BIP44_DERIVATION_PREFIX}/${addressIndex}'`; let derivationResult = derivePath(derivationPath, seed.toString("hex")); let key = derivationResult.key; @@ -55,7 +71,8 @@ export class Mnemonic { } getEntropy(): Uint8Array { - const entropy = mnemonicToEntropy(this.text); + loadBip39(); // Load bip39 when needed + const entropy = bip39.mnemonicToEntropy(this.text); return Buffer.from(entropy, "hex"); } From 9393d4f61926c56ead6991a4104ec98c10e69609 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 21 Oct 2024 15:16:50 +0300 Subject: [PATCH 114/118] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 97c9d84a5..c5cb620bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.11.0", + "version": "13.12.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.11.0", + "version": "13.12.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index d64a6d55e..72bc3ceaf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.11.0", + "version": "13.12.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", From f920ae61249a4288c3a292753783dc72822fdf33 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 21 Oct 2024 15:30:42 +0300 Subject: [PATCH 115/118] Remove redundant comment --- src/wallet/mnemonic.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/wallet/mnemonic.ts b/src/wallet/mnemonic.ts index 7d2024814..77c397b4c 100644 --- a/src/wallet/mnemonic.ts +++ b/src/wallet/mnemonic.ts @@ -7,6 +7,7 @@ const BIP44_DERIVATION_PREFIX = "m/44'/508'/0'/0'"; let bip39: any; +// Load bip39 when needed function loadBip39() { if (!bip39) { try { @@ -25,13 +26,13 @@ export class Mnemonic { } static generate(): Mnemonic { - loadBip39(); // Load bip39 when needed + loadBip39(); const text = bip39.generateMnemonic(MNEMONIC_STRENGTH); return new Mnemonic(text); } static fromString(text: string) { - loadBip39(); // Load bip39 when needed + loadBip39(); text = text.trim(); Mnemonic.assertTextIsValid(text); @@ -39,7 +40,7 @@ export class Mnemonic { } static fromEntropy(entropy: Uint8Array): Mnemonic { - loadBip39(); // Load bip39 when needed + loadBip39(); try { const text = bip39.entropyToMnemonic(Buffer.from(entropy)); return new Mnemonic(text); @@ -49,7 +50,7 @@ export class Mnemonic { } public static assertTextIsValid(text: string) { - loadBip39(); // Load bip39 when needed + loadBip39(); let isValid = bip39.validateMnemonic(text); if (!isValid) { @@ -58,7 +59,7 @@ export class Mnemonic { } deriveKey(addressIndex: number = 0, password: string = ""): UserSecretKey { - loadBip39(); // Load bip39 when needed + loadBip39(); let seed = bip39.mnemonicToSeedSync(this.text, password); let derivationPath = `${BIP44_DERIVATION_PREFIX}/${addressIndex}'`; let derivationResult = derivePath(derivationPath, seed.toString("hex")); @@ -71,7 +72,7 @@ export class Mnemonic { } getEntropy(): Uint8Array { - loadBip39(); // Load bip39 when needed + loadBip39(); const entropy = bip39.mnemonicToEntropy(this.text); return Buffer.from(entropy, "hex"); } From ced12ce99c79bf66db85ab1da089646c8beaa7bb Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 21 Oct 2024 16:21:34 +0300 Subject: [PATCH 116/118] Remove inner transaction and relayer field --- src/networkProviders/providers.dev.net.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/networkProviders/providers.dev.net.spec.ts b/src/networkProviders/providers.dev.net.spec.ts index b95994cb8..d08e5b4f7 100644 --- a/src/networkProviders/providers.dev.net.spec.ts +++ b/src/networkProviders/providers.dev.net.spec.ts @@ -452,8 +452,6 @@ describe("test network providers on devnet: Proxy and API", function () { guardian: "", guardianSignature: new Uint8Array(), options: 0, - relayer: "", - innerTransactions: [], }; const apiLegacyTxHash = await apiProvider.sendTransaction(transaction); From 17fe9af9eceb167061ef23125f04c89f7a0eca4b Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 28 Oct 2024 14:53:20 +0200 Subject: [PATCH 117/118] Bump version and update export --- package-lock.json | 4 ++-- package.json | 2 +- src/wallet/index.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5cb620bb..3fe6713c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "13.12.0", + "version": "13.13.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "13.12.0", + "version": "13.13.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index 72bc3ceaf..a68e5f55e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "13.12.0", + "version": "13.13.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com", diff --git a/src/wallet/index.ts b/src/wallet/index.ts index af9ecec6a..7207e0c14 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -1,3 +1,4 @@ +export * from "./crypto"; export * from "./mnemonic"; export * from "./pem"; export * from "./userKeys"; From 0f82f378ecdf18c7ed3df6599fab0c1686cf2b46 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 28 Oct 2024 14:54:27 +0200 Subject: [PATCH 118/118] Update versions --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3fe6713c9..23228e33b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1779,9 +1779,9 @@ } }, "node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", + "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", "dev": true, "dependencies": { "bn.js": "^4.11.9", @@ -6586,9 +6586,9 @@ } }, "elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", + "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", "dev": true, "requires": { "bn.js": "^4.11.9",