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

P256 support #35

Merged
merged 5 commits into from
Oct 10, 2023
Merged
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog

### 0.0.4

* fix ecdsa-p256 signature format to r || s
### 0.0.3

### Additions and Improvements

* Add P256 support for key creation and non-deterministic signing with ECDSA and SHA256

### Bug Fixes
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lacchain-key-manager",
"version": "0.0.2",
"version": "0.0.6",
"description": "Rest api for lacchain key manager",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
Expand Down
3 changes: 2 additions & 1 deletion src/constants/errorMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export enum ErrorsMessages {
USER_ALREADY_EXISTS = 'A user with this email is already registered',
KEY_NOT_FOUND = 'Key not found',
INVALID_ADDRESS = 'Invalid ripemd160 address',
INVALID_25519_TYPE = 'Invalid 25519 type'
INVALID_25519_TYPE = 'Invalid 25519 type',
INVALID_HEX_MESSAGE_ERROR = 'Expected a hex string'
}

export const Errors = {
Expand Down
8 changes: 6 additions & 2 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { DidJwtController } from './did.jwt.controller';
import { DidCommController } from './didcomm.controller';
import { Ed25519Controller } from './ed25519.controller';
import { Secp256k1SignerController } from './secp256k1-signer.controller';
import { P256Controller } from './p256.controller';
import { Secp256k1SignerController } from './secp256k1.signer.controller';
import { Secp256k1Controller } from './secp256k1.controller';
import { X25519Controller } from './x25519.controller';
import { P256SignerController } from './p256-signer.controller';

export const controllers = [
Secp256k1Controller,
X25519Controller,
Secp256k1SignerController,
DidJwtController,
DidCommController,
Ed25519Controller
Ed25519Controller,
P256Controller,
P256SignerController
];
29 changes: 29 additions & 0 deletions src/controllers/p256-signer.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
JsonController,
Post,
BadRequestError,
Body
} from 'routing-controllers';
import { Service } from 'typedi';
import { ErrorsMessages } from '../constants/errorMessages';
import { P256PlainMessageDTO } from '@dto/plainMessageDTO';
import { P256SignerServiceDb } from '@services/p256.signer.service';

@JsonController('/p256/sign')
@Service()
export class P256SignerController {
constructor(private readonly p256SignerService: P256SignerServiceDb) {}

@Post('/plain-message')
async signPlainMessage(
@Body({ validate: true }) message: P256PlainMessageDTO
): Promise<any> {
try {
return this.p256SignerService.signPlainMessage(message);
} catch (error: any) {
throw new BadRequestError(
error.detail ?? error.message ?? ErrorsMessages.INTERNAL_SERVER_ERROR
);
}
}
}
24 changes: 24 additions & 0 deletions src/controllers/p256.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { JsonController, Post, BadRequestError } from 'routing-controllers';
import { Service } from 'typedi';
import { ErrorsMessages } from '../constants/errorMessages';
import { P256DbService } from '@services/p256Db.service';

@JsonController('/p256')
@Service()
export class P256Controller {
private readonly p256Service: P256DbService;
constructor() {
this.p256Service = new P256DbService();
}

@Post('/')
async create(): Promise<any> {
try {
return this.p256Service.createKey();
} catch (error: any) {
throw new BadRequestError(
error.detail ?? error.message ?? ErrorsMessages.INTERNAL_SERVER_ERROR
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
} from 'routing-controllers';
import { Service } from 'typedi';
import { ErrorsMessages } from '../constants/errorMessages';
import { Secp256k1SignTransactionServiceDb } from '../services/signer.service';
// eslint-disable-next-line max-len
import { Secp256k1SignTransactionServiceDb } from '../services/secp256k1.tx.signer.service';
// eslint-disable-next-line max-len
import { Secp256k1SignLacchainTransactionServiceDb } from '../services/lacchain.signer.service';
import { EthereumTxDTO } from '../dto/signEthereumTxDTO';
Expand Down Expand Up @@ -57,7 +58,11 @@ export class Secp256k1SignerController {
@Body({ validate: true }) message: Secp256k1PlainMessageDTO
): Promise<any> {
try {
return this.secp256k1SignerService.signPlainMessage(message);
return this.secp256k1SignerService.signPlainMessage({
address: message.address,
keyId: message.keyId,
message: message.messageHash
});
} catch (error: any) {
throw new BadRequestError(
error.detail ?? error.message ?? ErrorsMessages.INTERNAL_SERVER_ERROR
Expand Down
29 changes: 28 additions & 1 deletion src/dto/plainMessageDTO.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
import { IsOptional, IsString } from 'class-validator';

export class PlainMessageDTO {
@IsString()
message!: string;
@IsString()
@IsOptional()
keyId?: string;
}

export class PlainMessageHashDTO {
@IsString()
messageHash!: string;
@IsString()
@IsOptional()
keyId?: string;
}

export class Secp256k1PlainMessageDTO extends PlainMessageDTO {
export class Secp256k1PlainMessageDTO extends PlainMessageHashDTO {
@IsString()
address!: string;
}

export class P256PlainMessageDTO extends PlainMessageDTO {
@IsString()
compressedPublicKey!: string;
@IsOptional()
@IsString()
encoding!:
| 'base64'
| 'base64url'
| 'hex'
| 'binary'
| 'utf8'
| 'utf-8'
| 'utf16le'
| 'latin1'
| 'ascii'
| 'ucs2'
| 'ucs-2';
}
9 changes: 8 additions & 1 deletion src/entities/ec.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { Base } from './base.entity';
export enum KeyType {
SECP256k1 = 'SECP256k1',
X25519 = 'X25519',
ED25519 = 'ED25519'
ED25519 = 'ED25519',
P256 = 'P256'
}
@Entity()
export class EC extends Base {
Expand All @@ -23,6 +24,12 @@ export class EC extends Base {
@Column({ name: 'public_key', unique: true, nullable: true })
publicKey!: string;

@Column({ name: 'x', unique: true, nullable: true })
x!: string;

@Column({ name: 'y', unique: true, nullable: true })
y!: string;

@Column({
name: 'key_type',
type: 'enum',
Expand Down
8 changes: 6 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export { Secp256k1DbService } from './services/secp256k1Db.service';
export { Generic25519DbService } from './services/generic25519Db.service';
export { P256DbService } from './services/p256Db.service';
export { ECService } from './services/interfaces/ec';
export { EC } from './entities/ec.entity';
export { Secp256k1SignTransactionServiceDb } from './services/signer.service';
// eslint-disable-next-line max-len
export { Secp256k1SignTransactionServiceDb } from './services/secp256k1.tx.signer.service';
// eslint-disable-next-line max-len
export { Secp256k1SignLacchainTransactionService } from './services/interfaces/secp256k1.lacchain.signer';
// eslint-disable-next-line max-len
Expand All @@ -17,7 +19,9 @@ export { IDidJwt } from './interfaces/did-jwt/did.jwt.interface';
export { IDidCommService } from './services/interfaces/didcomm.service';
export { DidCommDbService } from './services/didcomm/didcomm.db.service';
export { IDidCommToEncryptData } from './interfaces/didcomm/didcomm.interface';

// eslint-disable-next-line max-len
export { Secp256k1GenericSignerService } from './services/interfaces/secp256k1.generic.signer';
// eslint-disable-next-line max-len
export { Secp256k1GenericSignerServiceDb } from './services/lacchain.generic.signer.service';
export { Secp256k1GenericSignerServiceDb } from './services/secp256k1.signer.service';
export { P256SignerServiceDb } from './services/p256.signer.service';
20 changes: 18 additions & 2 deletions src/interfaces/signer/signer.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,30 @@ export interface ISignedTransaction {
}

export interface IPlainMessage {
messageHash: string;
message: string;
keyId?: string;
}

export interface ISignPlainMessageByAddress extends IPlainMessage {
address: string;
}

export interface ISecp256k1SignatureMessageResponse {
export interface ISignPlainMessageByCompressedPublicKey extends IPlainMessage {
compressedPublicKey: string;
encoding?:
| 'base64'
| 'base64url'
| 'hex'
| 'binary'
| 'utf8'
| 'utf-8'
| 'utf16le'
| 'latin1'
| 'ascii'
| 'ucs2'
| 'ucs-2';
}

export interface IECDSASignatureMessageResponse {
signature: string;
}
4 changes: 3 additions & 1 deletion src/services/interfaces/ec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ export interface IECFullKey {
key: string;
type: string;
publicKey: string;
x?: string;
y?: string;
}
export type key = {
keyId: string;
address: string;
address?: string;
publicKey: string;
type: string;
};
Expand Down
14 changes: 14 additions & 0 deletions src/services/interfaces/ecdsa.signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {
IECDSASignatureMessageResponse,
ISignPlainMessageByCompressedPublicKey
} from 'src/interfaces/signer/signer.interface';

export interface ECDSASignerService {
/**
*
* @param message - The 'hashed' message to be signed - MUST start with '0x'
*/
signPlainMessage(
message: ISignPlainMessageByCompressedPublicKey
): Promise<IECDSASignatureMessageResponse>;
}
7 changes: 4 additions & 3 deletions src/services/interfaces/secp256k1.generic.signer.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import {
ISignPlainMessageByAddress,
ISecp256k1SignatureMessageResponse
IECDSASignatureMessageResponse
} from 'src/interfaces/signer/signer.interface';

export interface Secp256k1GenericSignerService {
/**
*
* @param message - The 'hashed' message to be signed - MUST start with '0x'
* @param {ISignPlainMessageByAddress} message -
* The 'hashed' message to be signed - MUST start with '0x'
*/
signPlainMessage(
message: ISignPlainMessageByAddress
): Promise<ISecp256k1SignatureMessageResponse>;
): Promise<IECDSASignatureMessageResponse>;
}
79 changes: 79 additions & 0 deletions src/services/p256.signer.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { log4TSProvider } from '../config';
import { Service } from 'typedi';
import {
IECDSASignatureMessageResponse,
ISignPlainMessageByCompressedPublicKey
} from 'src/interfaces/signer/signer.interface';
import { ECDSASignerService } from './interfaces/ecdsa.signer';
import { P256DbService } from './p256Db.service';
import { createSign, KeyObject, webcrypto } from 'node:crypto';
import { BadRequestError, InternalServerError } from 'routing-controllers';
import { ErrorsMessages } from '../constants/errorMessages';
import { isHexString } from 'ethers';

@Service()
// eslint-disable-next-line max-len
export class P256SignerServiceDb implements ECDSASignerService {
protected readonly p256DbService = new P256DbService();
log = log4TSProvider.getLogger('p256-plain-message-signer');
/**
* @reference https://nodejs.org/docs/latest-v16.x/api/crypto.html#class-sign ...
* If encoding is not provided in {@link ISignPlainMessageByCompressedPublicKey}
* request, and the data is a string, an encoding of 'utf8'
* is enforced. If data is a Buffer, TypedArray, orDataView,
* then inputEncoding is ignored.
* @param {ISignPlainMessageByCompressedPublicKey} request - message request to sign.
* @return {Promise<IECDSASignatureMessageResponse>}
*/
async signPlainMessage(
request: ISignPlainMessageByCompressedPublicKey
): Promise<IECDSASignatureMessageResponse> {
const publicKey = request.compressedPublicKey;
const foundKey = await this.p256DbService.getKeyByCompressedPublicKey(
publicKey
);

if (!(foundKey.x && foundKey.y)) {
throw new InternalServerError(ErrorsMessages.INTERNAL_SERVER_ERROR);
}

const jwk = {
crv: 'P-256',
kty: 'EC',
x: Buffer.from(foundKey.x.replace('0x', ''), 'hex').toString('base64url'),
y: Buffer.from(foundKey.y.replace('0x', ''), 'hex').toString('base64url'),
d: Buffer.from(foundKey.key.replace('0x', ''), 'hex').toString(
'base64url'
)
};

const importedKey = await webcrypto.subtle.importKey(
'jwk',
jwk,
{ name: 'ECDSA', namedCurve: 'P-256' },
true,
['sign']
);

let message = request.message;
const sign = createSign('SHA256');
if (request.encoding && request.encoding === 'hex') {
message = message.replace('0x', '');
if (!isHexString(message.startsWith('0x') ? message : '0x' + message)) {
throw new BadRequestError(ErrorsMessages.INVALID_HEX_MESSAGE_ERROR);
}
}
sign.update(message, request.encoding ? request.encoding : 'utf8');
sign.end();
const sig =
'0x' +
sign.sign(
{ key: KeyObject.from(importedKey), dsaEncoding: 'ieee-p1363' },
'hex'
);
const signature = {
signature: sig
};
return signature;
}
}
Loading
Loading