Skip to content

Commit

Permalink
Merge commit '3e3db61a69e75abce880141545a12b0f0a1cb85a' into contract…
Browse files Browse the repository at this point in the history
…-wrapper
  • Loading branch information
claudiu725 committed Jul 28, 2021
2 parents 56be286 + 0d96935 commit 3ed5308
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 96 deletions.
1 change: 1 addition & 0 deletions src-network-wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export * from "./userWallet";
export * from "./userKeys";
export * from "./validatorKeys";
export * from "./userSigner";
export * from "./userVerifier";
export * from "./validatorSigner";
13 changes: 13 additions & 0 deletions src-network-wallet/userKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -60,6 +62,17 @@ export class UserPublicKey {
this.buffer = buffer;
}

verify(message: Buffer, signature: Buffer): boolean {
try {
const unopenedMessage = Buffer.concat([signature, message]);
const unsignedMessage = tweetnacl.sign.open(unopenedMessage, this.buffer);
return unsignedMessage != null;
} catch (err) {
Logger.error(err);
return false;
}
}

hex(): string {
return this.buffer.toString("hex");
}
Expand Down
29 changes: 29 additions & 0 deletions src-network-wallet/userVerifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {IVerifiable, IVerifier} from "../interface";
import {Address} from "../address";
import {UserPublicKey} from "./userKeys";

/**
* ed25519 signature verification
*/
export class UserVerifier implements IVerifier {

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.
*/
verify(message: IVerifiable): boolean {
return this.publicKey.verify(
message.serializeForSigning(this.publicKey.toAddress()),
Buffer.from(message.getSignature().hex(), 'hex'));
}
}
125 changes: 32 additions & 93 deletions src-network-wallet/userWallet.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
}

/**
Expand All @@ -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.edFromJSON(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]);
Expand All @@ -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 edFromJSON(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,
});
}

/**
Expand All @@ -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<Randomness>) {
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) });
}
}
22 changes: 19 additions & 3 deletions src-network-wallet/users.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { assert } from "chai";
import { loadMnemonic, loadPassword, loadTestWallets, TestWallet } 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";
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 alice: TestWallet, bob: TestWallet, carol: TestWallet;
Expand Down Expand Up @@ -120,6 +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 = new Address("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz");
let receiver = new Address("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r");

Expand All @@ -139,7 +143,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),
Expand Down Expand Up @@ -173,4 +177,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));
});
});

0 comments on commit 3ed5308

Please sign in to comment.