Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

1994 taquito signer error improvement #2490

Merged
merged 8 commits into from
May 19, 2023
6 changes: 3 additions & 3 deletions packages/taquito-core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ export class InvalidViewParameterError extends ParameterValidationError {

/**
* @category Error
* @description Error indicates an invalid key being passed or used
* @description Error indicates an invalid private key being passed or used
*/
export class InvalidKeyError extends ParameterValidationError {
constructor(public key: string, public errorDetail?: string) {
constructor(public errorDetail?: string) {
super();
this.name = 'InvalidKeyError';
this.message = `Invalid key "${key}"`;
this.message = `Invalid private key`;
errorDetail ? (this.message += `${errorDetail}`) : null;
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/taquito-core/test/errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,11 @@ describe('common error classes', () => {

it('should throw an InvalidKeyError', () => {
try {
throw new InvalidKeyError('foo');
throw new InvalidKeyError();
} catch (error) {
expect(error).toBeInstanceOf(ParameterValidationError);
expect(error).toBeInstanceOf(InvalidKeyError);
expect(error.message).toEqual(`Invalid key "foo"`);
expect(error.message).toEqual(`Invalid private key`);
}
});

Expand Down
18 changes: 12 additions & 6 deletions packages/taquito-signer/src/derivation-tools/ecdsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { HMAC } from '@stablelib/hmac';
import { SHA512 } from '@stablelib/sha512';
import BN from 'bn.js';
import { parseHex } from './utils';
import { InvalidBitSize, InvalidCurveError, InvalidSeedLengthError, PrivateKeyError } from '../errors';
import { InvalidBitSize, InvalidCurveError, InvalidSeedLengthError } from '../errors';
import { InvalidKeyError } from '@taquito/core';

export type CurveName = 'p256' | 'secp256k1';

Expand Down Expand Up @@ -38,18 +39,23 @@ export class PrivateKey implements ExtendedPrivateKey {
* @param seedSrc result of Bip39.mnemonicToSeed
* @param curve known supported curve p256 or secp256k1
* @returns instance of PrivateKey non-HD keys derived
* @throws {@link InvalidBitSize} | {@link InvalidCurveError} | {@link InvalidSeedLengthError}
*/
static fromSeed(seedSrc: Uint8Array | string, curve: CurveName): PrivateKey {
let seed = typeof seedSrc === 'string' ? parseHex(seedSrc) : seedSrc;
if (seed.length < minSeedSize || seed.length > maxSeedSize) {
throw new InvalidSeedLengthError(seed.length);
}
if (!Object.prototype.hasOwnProperty.call(seedKey, curve)) {
throw new InvalidCurveError(`unknown curve ${curve}`);
throw new InvalidCurveError(
`Unsupported curve "${curve}" expecting either "p256" or "secp256k1"`
);
}
const c = new ec(curve);
if (c.n?.bitLength() !== 256) {
throw new InvalidBitSize(`invalid curve bit size ${c.n?.bitLength()}`);
throw new InvalidBitSize(
`Invalid curve "${curve}" with bit size "${c.n?.bitLength()}" expecting bit size "256"`
);
}

const key = new TextEncoder().encode(seedKey[curve]);
Expand Down Expand Up @@ -87,7 +93,7 @@ export class PrivateKey implements ExtendedPrivateKey {
new DataView(data.buffer).setUint32(33, index);

let d: BN = new BN(0);
let chain: Uint8Array = new Uint8Array;
let chain: Uint8Array = new Uint8Array();
let i = 0;
while (i === 0) {
const sum = new HMAC(SHA512, this.chainCode).update(data).digest();
Expand Down Expand Up @@ -121,10 +127,11 @@ export class PrivateKey implements ExtendedPrivateKey {
/**
*
* @returns Uint8Array (if contains a private key)
* @throws {@link InvalidKeyError}
*/
bytes(): Uint8Array {
if (!this.keyPair.priv) {
throw new PrivateKeyError('not a private key');
throw new InvalidKeyError('missing private key');
}
// pad to 32 bytes as toArray() length argument seems to be ignored (BN bug)
const src = this.keyPair.priv.toArray();
Expand All @@ -133,4 +140,3 @@ export class PrivateKey implements ExtendedPrivateKey {
return out;
}
}

1 change: 1 addition & 0 deletions packages/taquito-signer/src/derivation-tools/ed25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class PrivateKey implements ExtendedPrivateKey {
*
* @param seedSrc result of Bip39.mnemonicToSeed
* @returns instance of PrivateKey
* @throws {@link InvalidSeedLengthError}
*/
static fromSeed(seedSrc: Uint8Array | string): PrivateKey {
const seed = typeof seedSrc === 'string' ? parseHex(seedSrc) : seedSrc;
Expand Down
1 change: 0 additions & 1 deletion packages/taquito-signer/src/ec-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export class ECKey {
const keyPrefix = key.substring(0, encrypted ? 5 : 4);
if (!isValidPrefix(keyPrefix)) {
throw new InvalidKeyError(
key,
invalidErrorDetail(ValidationResult.NO_PREFIX_MATCHED) +
` expecting one of the following prefix '${Prefix.SPSK}', '${Prefix.SPESK}', '${Prefix.P2SK}' or '${Prefix.P2ESK}'.`
);
Expand Down
18 changes: 14 additions & 4 deletions packages/taquito-signer/src/ed-key.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { hash } from '@stablelib/blake2b';
import { generateKeyPairFromSeed, sign } from '@stablelib/ed25519';
import { b58cencode, b58cdecode, prefix, buf2hex, isValidPrefix, Prefix } from '@taquito/utils';
import {
b58cencode,
b58cdecode,
prefix,
buf2hex,
isValidPrefix,
Prefix,
invalidErrorDetail,
ValidationResult,
} from '@taquito/utils';
import toBuffer from 'typedarray-to-buffer';
import { InvalidKeyError } from '@taquito/core';

Expand All @@ -23,16 +32,17 @@ export class Tz1 {
const keyPrefix = key.substring(0, encrypted ? 5 : 4);
if (!isValidPrefix(keyPrefix)) {
throw new InvalidKeyError(
key,
`With unsupported prefix expecting either '${Prefix.EDESK}' or '${Prefix.EDSK}'.`
`${invalidErrorDetail(ValidationResult.NO_PREFIX_MATCHED)} expecting either '${
Prefix.EDESK
}' or '${Prefix.EDSK}'.`
);
}

this._key = decrypt(b58cdecode(this.key, prefix[keyPrefix]));
this._publicKey = this._key.slice(32);

if (!this._key) {
throw new InvalidKeyError(key, 'Unable to decode');
throw new InvalidKeyError('unable to decode');
}

this.isInit = this.init();
Expand Down
73 changes: 51 additions & 22 deletions packages/taquito-signer/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,70 @@
export class InvalidMnemonicError extends Error {
public name = 'InvalidMnemonicError';
constructor(public message: string) {
super(message);
import { ParameterValidationError, UnsupportedActionError } from '@taquito/core';

/**
* @category Error
* @description Error indicates an invalid Mnemonic being passed or used
*/
export class InvalidMnemonicError extends ParameterValidationError {
constructor(public mnemonic: string) {
super();
this.name = 'InvalidMnemonicError';
this.message = `Invalid mnemonic "${mnemonic}"`;
}
}

export class InvalidBitSize extends Error {
public name = 'InvalidBitSize';
/**
* @category Error
* @description Error indicates a curve with incorrect bit size being passed or used
*/
export class InvalidBitSize extends ParameterValidationError {
constructor(public message: string) {
super(message);
super();
this.name = 'InvalidBitSize';
}
}

export class InvalidCurveError extends Error {
public name = 'InvalidCurveError';
constructor(public curve: string) {
super(`This Curve is not supported: ${curve}`);
/**
* @category Error
* @description Error indicates an unsupported cureve being passed or used
*/
export class InvalidCurveError extends ParameterValidationError {
constructor(public message: string) {
super();
this.name = 'InvalidCurveError';
}
}

export class InvalidSeedLengthError extends Error {
public name = 'InvalidSeedLengthError';
/**
* @category Error
* @description Error indicates a seed with invalid length being passed or used
*/
export class InvalidSeedLengthError extends ParameterValidationError {
constructor(public seedLength: number) {
super(`The seed has an invalid length: ${seedLength}`);
super();
this.name = 'InvalidSeedLengthError';
this.message = `Invalid seed length "${seedLength}" expecting length between 16 to 64.`;
}
}

export class PrivateKeyError extends Error {
public name = 'PrivateKeyError';
constructor(public message: string) {
super(message);
/**
* @category Error
* @description Error indicates a feature still under developement
*/
export class ToBeImplemented extends UnsupportedActionError {
constructor() {
super();
this.name = 'ToBeImplemented';
this.message = 'This feature is under developement';
}
}

export class ToBeImplemented extends Error {
public name = 'ToBeImplemented';
constructor() {
super('This feature is under developement');
/**
* @category Error
* @description Error indicates an invalid passphrase being passed or used
*/
export class InvalidPassphraseError extends ParameterValidationError {
constructor(public message: string) {
super();
this.name = 'InvalidPassphraseError';
}
}
15 changes: 9 additions & 6 deletions packages/taquito-signer/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { b58cencode, prefix } from "@taquito/utils";
import { PrivateKey as PrivateKeyEd } from "./derivation-tools/ed25519";
import { PrivateKey as PrivateKeyEc } from "./derivation-tools/ecdsa";
import { Path } from "./derivation-tools";
import { InvalidCurveError, ToBeImplemented } from "./errors";
import { b58cencode, prefix } from '@taquito/utils';
import { PrivateKey as PrivateKeyEd } from './derivation-tools/ed25519';
import { PrivateKey as PrivateKeyEc } from './derivation-tools/ecdsa';
import { Path } from './derivation-tools';
import { InvalidCurveError, ToBeImplemented } from './errors';

export type Curves = 'ed25519' | 'secp256k1' | 'p256' | 'bip25519';

Expand All @@ -13,6 +13,7 @@ export type Curves = 'ed25519' | 'secp256k1' | 'p256' | 'bip25519';
* @param derivationPath Tezos Requirement 44'/1729' for HD key address default 44'/1729'/0'/0'
* @param curve 'ed25519' | 'secp256k1' | 'p256''
* @returns final Derivation of HD keys tezos Secret key
* @throws {@link InvalidCurveError} | {@link ToBeImplemented}
*/
export const generateSecretKey = (seed: Uint8Array, derivationPath: string, curve: Curves) => {
const path = Path.fromString(derivationPath);
Expand All @@ -37,7 +38,9 @@ export const generateSecretKey = (seed: Uint8Array, derivationPath: string, curv
throw new ToBeImplemented();
}
default: {
throw new InvalidCurveError(curve);
throw new InvalidCurveError(
`Unsupported curve "${curve}" expecting one of the following "ed25519", "secp256k1", "p256" or "bip25519"`
);
}
}
};
40 changes: 21 additions & 19 deletions packages/taquito-signer/src/taquito-signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,29 @@
*/
import { openSecretBox } from '@stablelib/nacl';
import { hash } from '@stablelib/blake2b';
import { hex2buf, mergebuf, b58cencode, prefix, Prefix } from '@taquito/utils';
import {
hex2buf,
mergebuf,
b58cencode,
prefix,
Prefix,
invalidErrorDetail,
ValidationResult,
} from '@taquito/utils';
import toBuffer from 'typedarray-to-buffer';
import { Tz1 } from './ed-key';
import { Tz2, ECKey, Tz3 } from './ec-key';
import pbkdf2 from 'pbkdf2';
import * as Bip39 from 'bip39';
import { Curves, generateSecretKey } from './helpers';
import { InvalidMnemonicError } from './errors';
import { InvalidMnemonicError, InvalidPassphraseError } from './errors';
import { InvalidKeyError } from '@taquito/core';

export * from './import-key';
export { VERSION } from './version';
export * from './derivation-tools';
export * from './helpers';

/**
* @category Error
* @description Error that indicates an invalid passphrase being passed or used
*/
export class InvalidPassphraseError extends Error {
public name = 'InvalidPassphraseError';
constructor(public message: string) {
super(message);
}
}
export { InvalidPassphraseError } from './errors';

export interface FromMnemonicParams {
mnemonic: string;
Expand All @@ -41,14 +39,14 @@ export interface FromMnemonicParams {
* @description A local implementation of the signer. Will represent a Tezos account and be able to produce signature in its behalf
*
* @warn If running in production and dealing with tokens that have real value, it is strongly recommended to use a HSM backed signer so that private key material is not stored in memory or on disk
*
* @throws {@link InvalidMnemonicError}
*/
export class InMemorySigner {
private _key!: Tz1 | ECKey;

static fromFundraiser(email: string, password: string, mnemonic: string) {
if (!Bip39.validateMnemonic(mnemonic)) {
throw new InvalidMnemonicError(`Invalid mnemonic: ${mnemonic}`);
throw new InvalidMnemonicError(mnemonic);
}
const seed = Bip39.mnemonicToSeedSync(mnemonic, `${email}${password}`);
const key = b58cencode(seed.slice(0, 32), prefix.edsk2);
Expand All @@ -67,6 +65,7 @@ export class InMemorySigner {
* @param derivationPath default 44'/1729'/0'/0' (44'/1729' mandatory)
* @param curve currently only supported for tz1, tz2, tz3 addresses. soon bip25519
* @returns InMemorySigner
* @throws {@link InvalidMnemonicError}
*/
static fromMnemonic({
mnemonic,
Expand All @@ -77,7 +76,7 @@ export class InMemorySigner {
// check if curve is defined if not default tz1
if (!Bip39.validateMnemonic(mnemonic)) {
// avoiding exposing mnemonic again in case of mistake making invalid
throw new InvalidMnemonicError('Mnemonic provided is invalid');
throw new InvalidMnemonicError(mnemonic);
}
const seed = Bip39.mnemonicToSeedSync(mnemonic, password);

Expand All @@ -99,7 +98,7 @@ export class InMemorySigner {

if (encrypted) {
if (!passphrase) {
throw new InvalidPassphraseError('Encrypted key provided without a passphrase.');
throw new InvalidPassphraseError('No passphrase provided to decrypt encrypted key');
}

decrypt = (constructedKey: Uint8Array) => {
Expand Down Expand Up @@ -130,8 +129,11 @@ export class InMemorySigner {
break;
default:
throw new InvalidKeyError(
key,
`With unsupported prefix, expecting one of the following '${Prefix.EDESK}', '${Prefix.EDSK}', '${Prefix.SPSK}', '${Prefix.SPESK}', '${Prefix.P2SK}' or '${Prefix.P2ESK}'.`
`${invalidErrorDetail(
ValidationResult.NO_PREFIX_MATCHED
)} expecting one of the following '${Prefix.EDESK}', '${Prefix.EDSK}', '${
Prefix.SPSK
}', '${Prefix.SPESK}', '${Prefix.P2SK}' or '${Prefix.P2ESK}'.`
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/taquito-signer/test/taquito-signer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('inmemory-signer', () => {
it('Invalid key', (done) => {
expect(function () {
new InMemorySigner('test');
}).toThrow(`unsupported prefix`);
}).toThrow(`Invalid prefix`);
done();
});

Expand Down