Skip to content
This repository was archived by the owner on Jul 21, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 24 additions & 19 deletions src/keys/ed25519-class.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import errcode from 'err-code'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
import { sha256 } from 'multiformats/hashes/sha2'
import { base58btc } from 'multiformats/bases/base58'
import { identity } from 'multiformats/hashes/identity'
import { sha256 } from 'multiformats/hashes/sha2'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
import * as crypto from './ed25519.js'
import * as pbm from './keys.js'
import { exporter } from './exporter.js'
import * as pbm from './keys.js'

export class Ed25519PublicKey {
private readonly _key: Uint8Array
Expand Down Expand Up @@ -41,26 +41,32 @@ export class Ed25519PublicKey {
}

export class Ed25519PrivateKey {
private readonly _key: Uint8Array
private readonly _privateKey: Uint8Array
private readonly _publicKey: Uint8Array

// key - 64 byte Uint8Array containing private key
// key - 64 byte Uint8Array containing private key and public key concatenated or 32 byte Uint8Array containing private key
// publicKey - 32 byte Uint8Array containing public key
constructor (key: Uint8Array, publicKey: Uint8Array) {
this._key = ensureKey(key, crypto.privateKeyLength)
this._publicKey = ensureKey(publicKey, crypto.publicKeyLength)
constructor (key: Uint8Array, publicKey?: Uint8Array) {
if (key.length === crypto.privateKeyLength + crypto.publicKeyLength) {
const privateAndPublicKey = ensureKey(key, crypto.privateKeyLength + crypto.publicKeyLength)
this._privateKey = privateAndPublicKey.slice(0, crypto.privateKeyLength)
this._publicKey = privateAndPublicKey.slice(crypto.privateKeyLength)
} else {
this._privateKey = ensureKey(key, crypto.privateKeyLength)
this._publicKey = ensureKey(publicKey, crypto.publicKeyLength)
}
}

async sign (message: Uint8Array) { // eslint-disable-line require-await
return await crypto.hashAndSign(this._key, message)
async sign (message: Uint8Array) {
return await crypto.hashAndSign(this._privateKey, message)
}

get public () {
return new Ed25519PublicKey(this._publicKey)
}

marshal () {
return this._key
return crypto.concatKeys(this._privateKey, this._publicKey)
}

get bytes () {
Expand Down Expand Up @@ -109,15 +115,14 @@ export class Ed25519PrivateKey {
export function unmarshalEd25519PrivateKey (bytes: Uint8Array) {
// Try the old, redundant public key version
if (bytes.length > crypto.privateKeyLength) {
bytes = ensureKey(bytes, crypto.privateKeyLength + crypto.publicKeyLength)
const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength)
const publicKeyBytes = bytes.slice(crypto.privateKeyLength, bytes.length)
const publicKeyBytes = bytes.slice(crypto.privateKeyLength, crypto.privateKeyLength + crypto.publicKeyLength)
return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
}

bytes = ensureKey(bytes, crypto.privateKeyLength)
const privateKeyBytes = bytes.slice(0, crypto.privateKeyLength)
const publicKeyBytes = bytes.slice(crypto.publicKeyLength)
const publicKeyBytes = bytes.slice(crypto.privateKeyLength)
return new Ed25519PrivateKey(privateKeyBytes, publicKeyBytes)
}

Expand All @@ -127,16 +132,16 @@ export function unmarshalEd25519PublicKey (bytes: Uint8Array) {
}

export async function generateKeyPair () {
const { privateKey, publicKey } = await crypto.generateKey()
return new Ed25519PrivateKey(privateKey, publicKey)
const { privateKey } = await crypto.generateKey()
return new Ed25519PrivateKey(privateKey)
}

export async function generateKeyPairFromSeed (seed: Uint8Array) {
const { privateKey, publicKey } = await crypto.generateKeyFromSeed(seed)
return new Ed25519PrivateKey(privateKey, publicKey)
const { privateKey } = await crypto.generateKeyFromSeed(seed)
return new Ed25519PrivateKey(privateKey)
}

function ensureKey (key: Uint8Array, length: number) {
function ensureKey (key: Uint8Array|undefined, length: number) {
key = Uint8Array.from(key ?? [])
if (key.length !== length) {
throw errcode(new Error(`Key must be a Uint8Array of length ${length}, got ${key.length}`), 'ERR_INVALID_KEY_TYPE')
Expand Down
17 changes: 11 additions & 6 deletions src/keys/ed25519.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import * as ed from '@noble/ed25519'

const PUBLIC_KEY_BYTE_LENGTH = 32
const PRIVATE_KEY_BYTE_LENGTH = 64 // private key is actually 32 bytes but for historical reasons we concat private and public keys
const PRIVATE_KEY_BYTE_LENGTH = 32
const KEYS_BYTE_LENGTH = 32

export { PUBLIC_KEY_BYTE_LENGTH as publicKeyLength }
export { PRIVATE_KEY_BYTE_LENGTH as privateKeyLength }

/**
*
* Private key in returned object is actually private and public key concatenated
*/
export async function generateKey () {
// the actual private key (32 bytes)
const privateKeyRaw = ed.utils.randomPrivateKey()
Expand All @@ -22,7 +26,8 @@ export async function generateKey () {
}

/**
* Generate keypair from a 32 byte uint8array
* Generate keypair from a 32 byte uint8array.
* Private key in returned object is actually private and public key concatenated
*/
export async function generateKeyFromSeed (seed: Uint8Array) {
if (seed.length !== KEYS_BYTE_LENGTH) {
Expand Down Expand Up @@ -53,11 +58,11 @@ export async function hashAndVerify (publicKey: Uint8Array, sig: Uint8Array, msg
return await ed.verify(sig, msg, publicKey)
}

function concatKeys (privateKeyRaw: Uint8Array, publicKey: Uint8Array) {
const privateKey = new Uint8Array(PRIVATE_KEY_BYTE_LENGTH)
for (let i = 0; i < KEYS_BYTE_LENGTH; i++) {
export function concatKeys (privateKeyRaw: Uint8Array, publicKey: Uint8Array) {
const privateKey = new Uint8Array(PRIVATE_KEY_BYTE_LENGTH + PUBLIC_KEY_BYTE_LENGTH)
for (let i = 0; i < PRIVATE_KEY_BYTE_LENGTH; i++) {
privateKey[i] = privateKeyRaw[i]
privateKey[KEYS_BYTE_LENGTH + i] = publicKey[i]
privateKey[PRIVATE_KEY_BYTE_LENGTH + i] = publicKey[i]
}
return privateKey
}
4 changes: 2 additions & 2 deletions src/keys/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ function typeToKey (type: string) {
}

// Generates a keypair of the given type and bitsize
export async function generateKeyPair (type: KeyTypes, bits?: number): Promise<PrivateKey> { // eslint-disable-line require-await
export async function generateKeyPair (type: KeyTypes, bits?: number): Promise<PrivateKey> {
return await typeToKey(type).generateKeyPair(bits ?? 2048)
}

// Generates a keypair of the given type and bitsize
// seed is a 32 byte uint8array
export async function generateKeyPairFromSeed (type: KeyTypes, seed: Uint8Array, bits?: number): Promise<PrivateKey> { // eslint-disable-line require-await
export async function generateKeyPairFromSeed (type: KeyTypes, seed: Uint8Array, bits?: number): Promise<PrivateKey> {
if (type.toLowerCase() !== 'ed25519') {
throw errcode(new Error('Seed key derivation is unimplemented for RSA or secp256k1'), 'ERR_UNSUPPORTED_KEY_DERIVATION_TYPE')
}
Expand Down
18 changes: 18 additions & 0 deletions test/keys/ed25519.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as crypto from '../../src/index.js'
import fixtures from '../fixtures/go-key-ed25519.js'
import { testGarbage } from '../helpers/test-garbage-error-handling.js'
import { Ed25519PrivateKey } from '../../src/keys/ed25519-class.js'
import { generateKey } from '../../src/keys/ed25519.js'

const ed25519 = crypto.keys.supportedKeys.ed25519

Expand All @@ -29,6 +30,23 @@ describe('ed25519', function () {
expect(digest).to.have.length(34)
})

it('initializes with private + public key', async () => {
const key = new Ed25519PrivateKey((await generateKey()).privateKey)
expect(key).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
const digest = await key.hash()
expect(digest).to.have.length(34)
})

it('initializes with private and separate public key', async () => {
const keyRaw = (await generateKey()).privateKey
const privateKey = keyRaw.slice(0, 32)
const publicKey = keyRaw.slice(32)
const key = new Ed25519PrivateKey(privateKey, publicKey)
expect(key).to.be.an.instanceof(ed25519.Ed25519PrivateKey)
const digest = await key.hash()
expect(digest).to.have.length(34)
})

it('generates a valid key from seed', async () => {
const seed = crypto.randomBytes(32)
const seededkey = await crypto.keys.generateKeyPairFromSeed('Ed25519', seed, 512)
Expand Down