Skip to content

Commit

Permalink
[xp] refactor keys class
Browse files Browse the repository at this point in the history
  • Loading branch information
peterjah committed May 6, 2024
1 parent e0f6fcf commit 58bcca0
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 96 deletions.
21 changes: 4 additions & 17 deletions packages/massa-web3/src/experimental/account.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Version } from './crypto/interfaces/versioner'
import Sealer from './crypto/interfaces/sealer'
import VarintVersioner from './crypto/varintVersioner'
import { PasswordSeal } from './crypto/passwordSeal'
import { Address, PrivateKey, PublicKey, Signature } from './basicElements'
import { readFileSync, existsSync } from 'fs'
Expand Down Expand Up @@ -186,13 +185,9 @@ export class Account {
keystore.Nonce
)
const privateKeyBytes = await passwordSeal.unseal(keystore.CipheredData)
const privateKey = PrivateKey.fromBytes(privateKeyBytes, Version.V0)
const privateKey = PrivateKey.fromBytes(privateKeyBytes)
const publicKey = PublicKey.fromBytes(
// TODO: The PublicKey class should be refactored to ensure consistency between the fromBytes and toBytes methods,
// similar to what was done for the address class.
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
new Uint8Array(keystore.PublicKey).subarray(1),
Version.V0
Uint8Array.from(keystore.PublicKey)
)
const address = publicKey.getAddress()
// TODO: add a consistency check with the address in the keystore
Expand All @@ -210,18 +205,10 @@ export class Account {
)

const privateKeyBytes = await passwordSeal.unseal(keystore.CipheredData)
const varintVersioner = new VarintVersioner()
const { data: bytes, version: numberVersion } =
varintVersioner.extract(privateKeyBytes)

const version = numberVersion as Version
const privateKey = PrivateKey.fromBytes(bytes, version)
const privateKey = PrivateKey.fromBytes(privateKeyBytes)
const publicKey = PublicKey.fromBytes(
// TODO: The PublicKey class should be refactored to ensure consistency between the fromBytes and toBytes methods,
// similar to what was done for the address class.
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
new Uint8Array(keystore.PublicKey).subarray(1),
Version.V0
Uint8Array.from(keystore.PublicKey)
)
const address = publicKey.getAddress()
// TODO: add a consistency check with the address in the keystore
Expand Down
128 changes: 77 additions & 51 deletions packages/massa-web3/src/experimental/basicElements/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,25 @@ import Serializer from '../crypto/interfaces/serializer'
import Signer from '../crypto/interfaces/signer'
import { Version, Versioner } from '../crypto/interfaces/versioner'
import VarintVersioner from '../crypto/varintVersioner'
import { mustExtractPrefix, extractData } from './internal'

const PRIVATE_KEY_PREFIX = 'S'
const PUBLIC_KEY_PREFIX = 'P'

/**
* Get the version from string or bytes key.
*
* @remarks
* For now the function is common for private & public key but it might change in the futur.
*
* @returns the key version.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function getVersion(data: string | Uint8Array): Version {
// When a new version will come, implement the logic to detect version here
// This should be done without serializer and versionner as they are potentially not known at this point
return Version.V0
}

/**
* A class representing a private key.
*
Expand All @@ -27,7 +41,9 @@ const PUBLIC_KEY_PREFIX = 'P'
* - Voila! The code will automatically handle the new version.
*/
export class PrivateKey {
public bytes: Uint8Array
// The key in byte format. Version included.
private bytes: Uint8Array
private prefix = PRIVATE_KEY_PREFIX

// eslint-disable-next-line max-params
protected constructor(
Expand Down Expand Up @@ -60,42 +76,46 @@ export class PrivateKey {
}
}

private checkPrefix(str: string): void {
if (!str.startsWith(this.prefix)) {
throw new Error(
`invalid private key prefix: ${this.prefix} was expected.`
)
}
}

/**
* Initializes a new private key object from a serialized string.
*
* @param str - The serialized private key string.
* @param version - The version of the private key. If not defined, the last version will be used.
*
* @returns A new private key instance.
*
* @throws If the private key prefix is invalid.
*/
public static fromString(str: string, version?: Version): PrivateKey {
const privateKey = PrivateKey.initFromVersion(version)

public static fromString(str: string): PrivateKey {
try {
mustExtractPrefix(str, PRIVATE_KEY_PREFIX)
privateKey.bytes = extractData(
privateKey.serializer,
privateKey.versioner,
str.slice(PRIVATE_KEY_PREFIX.length),
privateKey.version
const version = getVersion(str)
const privateKey = PrivateKey.initFromVersion(version)
privateKey.checkPrefix(str)
privateKey.bytes = privateKey.serializer.deserialize(
str.slice(privateKey.prefix.length)
)
return privateKey
} catch (e) {
throw new Error(`invalid private key string: ${e.message}`)
}
return privateKey
}

/**
* Initializes a new private key object from a raw byte array and a version.
* Initializes a new private key object from a byte array.
*
* @param bytes - The raw bytes without any prefix version.
* @param version - The version of the private key. If not defined, the last version will be used.
* @param bytes - The private key in byte format.
*
* @returns A new private key instance.
*/
public static fromBytes(bytes: Uint8Array, version?: Version): PrivateKey {
public static fromBytes(bytes: Uint8Array): PrivateKey {
const version = getVersion(bytes)
const privateKey = PrivateKey.initFromVersion(version)
privateKey.bytes = bytes
return privateKey
Expand Down Expand Up @@ -125,7 +145,8 @@ export class PrivateKey {
*/
public static generate(version?: Version): PrivateKey {
const privateKey = PrivateKey.initFromVersion(version)
privateKey.bytes = privateKey.signer.generatePrivateKey()
const rawBytes = privateKey.signer.generatePrivateKey()
privateKey.bytes = privateKey.versioner.attach(privateKey.version, rawBytes)
return privateKey
}

Expand All @@ -150,22 +171,18 @@ export class PrivateKey {
* @returns The signature byte array.
*/
public async sign(message: Uint8Array): Promise<Signature> {
const signatureRawBytes = await this.signer.sign(
this.bytes,
this.hasher.hash(message)
)
return Signature.fromBytes(
this.versioner.attach(this.version, signatureRawBytes)
)
const { data } = this.versioner.extract(this.bytes)
const signature = await this.signer.sign(data, this.hasher.hash(message))
return Signature.fromBytes(this.versioner.attach(this.version, signature))
}

/**
* Versions the private key bytes.
* Private key in bytes.
*
* @returns The versioned private key bytes.
*/
public toBytes(): Uint8Array {
return this.versioner.attach(this.version, this.bytes)
return this.bytes
}

/**
Expand All @@ -179,7 +196,7 @@ export class PrivateKey {
* @returns The serialized private key string.
*/
public toString(): string {
return `${PRIVATE_KEY_PREFIX}${this.serializer.serialize(this.toBytes())}`
return `${this.prefix}${this.serializer.serialize(this.bytes)}`
}
}

Expand All @@ -198,7 +215,9 @@ export class PrivateKey {
* - Voila! The code will automatically handle the new version.
*/
export class PublicKey {
public bytes: Uint8Array
// The key in byte format. Version included.
private bytes: Uint8Array
private prefix = PUBLIC_KEY_PREFIX

// eslint-disable-next-line max-params
protected constructor(
Expand Down Expand Up @@ -233,43 +252,44 @@ export class PublicKey {
}
}

private checkPrefix(str: string): void {
if (!str.startsWith(this.prefix)) {
throw new Error(`invalid public key prefix: ${this.prefix} was expected.`)
}
}

/**
* Initializes a new public key object from a serialized string.
*
* @param str - The serialized public key string.
* @param version - The version of the public key. If not defined, the last version will be used.
*
* @returns A new public key instance.
*
* @throws If the public key string is invalid.
* @throws If the public key prefix is invalid.
*/
public static fromString(str: string, version?: Version): PublicKey {
const publicKey = PublicKey.initFromVersion(version)

public static fromString(str: string): PublicKey {
try {
mustExtractPrefix(str, PUBLIC_KEY_PREFIX)
publicKey.bytes = extractData(
publicKey.serializer,
publicKey.versioner,
str.slice(PUBLIC_KEY_PREFIX.length),
publicKey.version
const version = getVersion(str)
const publicKey = PublicKey.initFromVersion(version)
publicKey.checkPrefix(str)
publicKey.bytes = publicKey.serializer.deserialize(
str.slice(publicKey.prefix.length)
)
return publicKey
} catch (e) {
throw new Error(`invalid public key string: ${e.message}`)
}

return publicKey
}

/**
* Initializes a new public key object from a raw byte array and a version.
* Initializes a new public key object from a byte array.
*
* @param bytes - The raw bytes without any prefix version.
* @param version - The version of the public key. If not defined, the last version will be used.
* @param bytes - The public key in byte format.
*
* @returns A new public key instance.
*/
public static fromBytes(bytes: Uint8Array, version?: Version): PublicKey {
public static fromBytes(bytes: Uint8Array): PublicKey {
const version = getVersion(bytes)
const publicKey = PublicKey.initFromVersion(version)
publicKey.bytes = bytes
return publicKey
Expand All @@ -286,7 +306,12 @@ export class PublicKey {
privateKey: PrivateKey
): Promise<PublicKey> {
const publicKey = PublicKey.initFromVersion()
publicKey.bytes = await publicKey.signer.getPublicKey(privateKey.bytes)
const { data } = publicKey.versioner.extract(privateKey.toBytes())
const publicKeyBytes = await publicKey.signer.getPublicKey(data)
publicKey.bytes = publicKey.versioner.attach(
publicKey.version,
publicKeyBytes
)
return publicKey
}

Expand Down Expand Up @@ -316,20 +341,21 @@ export class PublicKey {
signature: Signature
): Promise<boolean> {
const { data: rawSignature } = this.versioner.extract(signature.toBytes())
const { data: rawPublicKey } = this.versioner.extract(this.bytes)
return await this.signer.verify(
this.bytes,
rawPublicKey,
this.hasher.hash(data),
rawSignature
)
}

/**
* Versions the public key bytes.
* Public key in bytes.
*
* @returns The versioned public key bytes.
*/
public toBytes(): Uint8Array {
return this.versioner.attach(this.version, this.bytes)
return this.bytes
}

/**
Expand All @@ -343,6 +369,6 @@ export class PublicKey {
* @returns The serialized public key string.
*/
public toString(): string {
return `${PUBLIC_KEY_PREFIX}${this.serializer.serialize(this.toBytes())}`
return `${this.prefix}${this.serializer.serialize(this.bytes)}`
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ export class Signature {
const version = getVersion(bytes)
const signature = Signature.initFromVersion(version)
signature.bytes = bytes
const { version: extractedVersion } = signature.versioner.extract(bytes)

// safety check
const { version: extractedVersion } = signature.versioner.extract(bytes)
if (extractedVersion !== version) {
throw new Error(
`invalid version: ${version}. ${signature.version} was expected.`
Expand Down
4 changes: 2 additions & 2 deletions packages/massa-web3/test/experimental/unit/account.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Account, AccountKeyStore } from '../../../src/experimental/account'
import { Version } from '../../../src/experimental/crypto/interfaces/versioner'
import path from 'path'

describe('Basic use cases', () => {
test('Account - from private key', async () => {
describe('Account tests', () => {
test('from private key', async () => {
const account = await Account.fromPrivateKey(
'S12jWf59Yzf2LimL89soMnAP2VEBDBpfCbZLoEFo36CxEL3j92rZ'
)
Expand Down
31 changes: 7 additions & 24 deletions packages/massa-web3/test/experimental/unit/key.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import {
PrivateKey,
PublicKey,
} from '../../../src/experimental/basicElements/keys'
import { Version } from '../../../src/experimental/crypto/interfaces/versioner'

const invalidVersion = -1 as Version

describe('PrivateKey and PublicKey tests', () => {
let privateKey: PrivateKey
Expand All @@ -19,14 +16,14 @@ describe('PrivateKey and PublicKey tests', () => {
describe('Conversion to and from Bytes', () => {
test('PublicKey toBytes and fromBytes', async () => {
const publicKey = await PublicKey.fromPrivateKey(privateKey)
const newPubKeyBytes = PublicKey.fromBytes(publicKey.bytes)
expect(newPubKeyBytes.bytes).toEqual(publicKey.bytes)
const newPubKeyBytes = PublicKey.fromBytes(publicKey.toBytes())
expect(newPubKeyBytes.toBytes()).toEqual(publicKey.toBytes())
})

test('PrivateKey toBytes and fromBytes', async () => {
const privateKey = PrivateKey.generate()
const newPKeyBytes = PrivateKey.fromBytes(privateKey.bytes)
expect(newPKeyBytes.bytes).toEqual(privateKey.bytes)
const newPKeyBytes = PrivateKey.fromBytes(privateKey.toBytes())
expect(newPKeyBytes.toBytes()).toEqual(privateKey.toBytes())
})
})

Expand Down Expand Up @@ -79,23 +76,9 @@ describe('PrivateKey and PublicKey tests', () => {
test('fromString throws error for invalid public key string', () => {
const invalidPublicKeyString = 'invalidPublicKey'

expect(() =>
PublicKey.fromString(invalidPublicKeyString, Version.V0)
).toThrow(/invalid public key string/)
})

test('fromString throws error for invalid public key version', () => {
const publicKeyString = publicKey.toString()
expect(() =>
PublicKey.fromString(publicKeyString, invalidVersion)
).toThrow(/unsupported version/)
})
test('fromString throws error for invalid private key version', () => {
const privateKeyString = privateKey.toString()

expect(() =>
PrivateKey.fromString(privateKeyString, invalidVersion)
).toThrow(/unsupported version/)
expect(() => PublicKey.fromString(invalidPublicKeyString)).toThrow(
/invalid public key string/
)
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { PrivateKey, Address } from '../../../src/experimental/basicElements'
import 'dotenv/config'

describe('Unit tests', () => {
describe('Operation manager tests', () => {
test('serialize - transfer', async () => {
const transfer: TransferOperation = {
fee: 1n,
Expand Down

0 comments on commit 58bcca0

Please sign in to comment.