Skip to content

Commit

Permalink
Simplify verify (#143)
Browse files Browse the repository at this point in the history
* renamed lib and divided tests

* moved test to lib dir

* removed duplication, signs viem keys

* removed alg import from shared

* lint

* alg import

* another alg import

* signing header + payload, improved tests

* fixed imports

* removed useless config change

* import in tests

* Alg import in integration test

* remove unecessary verifying step and args

* renamed encoded token from rawToken to jwt

* rename hash-request to hash

* removed console.log
  • Loading branch information
Ptroger authored Mar 1, 2024
1 parent 79338e0 commit f581e6b
Show file tree
Hide file tree
Showing 17 changed files with 108 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Feed, HistoricalTransfer, Signature } from '@narval/policy-engine-shared'
import { Alg, hashRequest } from '@narval/signature'
import { Alg, hash } from '@narval/signature'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { mapValues, omit } from 'lodash/fp'
Expand Down Expand Up @@ -30,7 +30,7 @@ export class HistoricalTransferFeedService implements DataFeed<HistoricalTransfe
async sign(data: HistoricalTransfer[]): Promise<Signature> {
const account = privateKeyToAccount(this.getPrivateKey())
const sig = await account.signMessage({
message: hashRequest(data)
message: hash(data)
})

return {
Expand Down
4 changes: 2 additions & 2 deletions apps/armory/src/data-feed/core/service/price-feed.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Action, AssetId, Feed, Signature } from '@narval/policy-engine-shared'
import { Alg, hashRequest } from '@narval/signature'
import { Alg, hash } from '@narval/signature'
import { InputType, Intents, safeDecode } from '@narval/transaction-request-intent'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
Expand Down Expand Up @@ -29,7 +29,7 @@ export class PriceFeedService implements DataFeed<Prices> {
async sign(data: Prices): Promise<Signature> {
const account = privateKeyToAccount(this.getPrivateKey())
const sig = await account.signMessage({
message: hashRequest(data)
message: hash(data)
})

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Decision, EvaluationResponse, Feed, Prices } from '@narval/policy-engine-shared'
import { Alg, hashRequest } from '@narval/signature'
import { Alg, hash } from '@narval/signature'
import { Test } from '@nestjs/testing'
import { MockProxy, mock } from 'jest-mock-extended'
import { PrivateKeyAccount, generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
Expand Down Expand Up @@ -99,8 +99,8 @@ describe(ClusterService.name, () => {
authzRequest: AuthorizationRequest,
partial?: Partial<EvaluationResponse>
): Promise<EvaluationResponse> => {
const hash = hashRequest(authzRequest.request)
const signature = await nodeAccount.signMessage({ message: hash })
const requestHash = hash(authzRequest.request)
const signature = await nodeAccount.signMessage({ message: requestHash })

return {
decision: Decision.PERMIT,
Expand Down Expand Up @@ -168,7 +168,7 @@ describe(ClusterService.name, () => {
it('throws when node attestation is invalid', async () => {
const permit = await generateEvaluationResponse(nodeAccount, authzRequest)
const signature = await nodeAccount.signMessage({
message: hashRequest({ notTheOriginalRequest: true })
message: hash({ notTheOriginalRequest: true })
})

authzApplicationClientMock.evaluation.mockResolvedValue({
Expand Down
6 changes: 3 additions & 3 deletions apps/armory/src/orchestration/core/service/cluster.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Decision, EvaluationRequest, EvaluationResponse } from '@narval/policy-engine-shared'
import { hashRequest } from '@narval/signature'
import { hash } from '@narval/signature'
import { Injectable, Logger } from '@nestjs/common'
import { zip } from 'lodash/fp'
import { ClusterNotFoundException } from '../../core/exception/cluster-not-found.exception'
Expand Down Expand Up @@ -120,10 +120,10 @@ export class ClusterService {
}

private async recoverPubKey(response: EvaluationResponse) {
const hash = hashRequest(response.request) as `0x${string}`
const requestHash = hash(response.request) as `0x${string}`

return recoverMessageAddress({
message: hash,
message: requestHash,
signature: response.attestation?.sig as `0x${string}`
})
}
Expand Down
4 changes: 2 additions & 2 deletions apps/policy-engine/src/app/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
Request,
Signature
} from '@narval/policy-engine-shared'
import { Alg, hashRequest } from '@narval/signature'
import { Alg, hash } from '@narval/signature'
import { safeDecode } from '@narval/transaction-request-intent'
import {
BadRequestException,
Expand Down Expand Up @@ -159,7 +159,7 @@ export class AppService {
}: EvaluationRequest): Promise<EvaluationResponse> {
// Pre-Process
// verify the signatures of the Principal and any Approvals
const verificationMessage = hashRequest(request)
const verificationMessage = hash(request)

const principalCredential = await this.#verifySignature(authentication, verificationMessage)
if (!principalCredential) throw new Error(`Could not find principal`)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Action, EvaluationRequest, FIXTURE, Request, TransactionRequest } from '@narval/policy-engine-shared'
import { Alg, hashRequest } from '@narval/signature'
import { Alg, hash } from '@narval/signature'
import { toHex } from 'viem'

export const ONE_ETH = BigInt('1000000000000000000')
Expand All @@ -22,7 +22,7 @@ export const generateInboundRequest = async (): Promise<EvaluationRequest> => {
resourceId: FIXTURE.WALLET.Engineering.id
}

const message = hashRequest(request)
const message = hash(request)

const aliceSignature = await FIXTURE.ACCOUNT.Alice.signMessage({ message })
const bobSignature = await FIXTURE.ACCOUNT.Bob.signMessage({ message })
Expand Down
63 changes: 61 additions & 2 deletions packages/signature/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,65 @@
# Signature
# Signature Library

Lib to decode a signature
The Signature library is designed to decode and verify JWTs, providing robust support for various cryptographic operations, including signature verification and JWT decoding without signature validation. It's built with flexibility in mind, supporting Ethereum's EOA signatures and traditional JWT signing algorithms.

## Features

- Decode JWTs without verifying their signature.
- Verify JWT signatures with support for Ethereum EOA and traditional algorithms.
- Generate SHA256 hashes for arbitrary objects, with support for BigInt primitives.
- Sign requests using private keys and predefined algorithms.

## Usage

### Signing

```
import { Alg, sign } from '@narval/signature';
const signingInput = {
request: {
message: 'stuff'
}, // this can be anything
privateKey: 'your-private-key-pem-or-hex',
algorithm: Alg.ES256K, // Example algorithm
kid: 'your-key-id',
iat: new Date(),
exp: new Date(new Date().getTime() + 60 * 60 * 1000), // Expires in 1 hour
};
sign(signingInput)
.then(signedJwt => console.log(signedJwt))
.catch(error => console.error(error));
```

### Decoding

```
import { decode } from '@narval/signature';
const jwt = 'your.jwt.string';
try {
const decodedJwt = decode(jwt);
console.log(decodedJwt);
} catch (error) {
console.error(error);
}
```

### Verifying

```
import { sign } from '@narval/signature';
const verificationInput = {
rawToken: 'your.jwt.string',
publicKey: 'your-public-key-in-pem-or-hex', // !! PubKey NOT eoa address
};
verify(verificationInput)
.then(decodedJwt => console.log(decodedJwt))
.catch(error => console.error(error));
```

## Testing

Expand Down
2 changes: 1 addition & 1 deletion packages/signature/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { decode } from './lib/decode'
export { hashRequest } from './lib/hash-request'
export { hash } from './lib/hash-request'
export { sign } from './lib/sign'
export * from './lib/types'
export { verify } from './lib/verify'
6 changes: 2 additions & 4 deletions packages/signature/src/lib/__test__/unit/eoa-keys.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ describe('flow with viem keypairs', () => {
}
const jwt = await sign(signingInput)
const verificationInput: VerificationInput = {
request: REQUEST,
rawToken: jwt,
publicKey: viemPk,
algorithm: viemPkAlg
jwt,
publicKey: viemPk
}
const verifiedJwt = await verify(verificationInput)
expect(verifiedJwt).toEqual(expected)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { hashRequest } from '../../hash-request'
import { hash } from '../../hash-request'

describe('hashRequest', () => {
it('hashes the given object', () => {
expect(
hashRequest({
hash({
a: 'a',
b: 1,
c: false
Expand All @@ -12,7 +12,7 @@ describe('hashRequest', () => {
})

it('hashes the given array', () => {
expect(hashRequest(['a', 1, false])).toEqual('cdd23dea0598c5ffc66b6a53f9dc7448a87b47454f209caa310e21da91754173')
expect(hash(['a', 1, false])).toEqual('cdd23dea0598c5ffc66b6a53f9dc7448a87b47454f209caa310e21da91754173')
})

it('hashes two objects deterministically', () => {
Expand All @@ -35,6 +35,6 @@ describe('hashRequest', () => {
a: 'a'
}

expect(hashRequest(a)).toEqual(hashRequest(b))
expect(hash(a)).toEqual(hash(b))
})
})
4 changes: 2 additions & 2 deletions packages/signature/src/lib/__test__/unit/mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { hashRequest } from '../../hash-request'
import { hash } from '../../hash-request'
import { Alg } from '../../types'

export const ALGORITHM = Alg.ES256
Expand Down Expand Up @@ -27,7 +27,7 @@ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgUDs3iCP93sknEZ+c
DcdsS+UdaUgKK0XVajrBWZbQYWehRANCAAT4HK9PrjSP2z5sFVzU+NOU6Jlr2jHK
56woxq1wNvSAh4JZtYO4SLJWaSB8YUD3caXqgH3gSIHYQWm+EY2h4nAc
-----END PRIVATE KEY-----`
export const HASH = hashRequest(REQUEST)
export const HASH = hash(REQUEST)
export const SIGNED_TOKEN =
'eyJhbGciOiJFUzI1NiIsImtpZCI6InRlc3Qta2lkIn0.eyJyZXF1ZXN0SGFzaCI6IjY4NjMxYmIyMmIxNzFkMjk2YTUyMmJiNmMzMjQ4MDU1NTk3YmY2M2VhYzJiYTk1ZjFmZDAyYTQ4YWUxZWRmOGMiLCJpYXQiOjE3MzM4NzUyMDAsImV4cCI6MTczMzk2MTYwMH0.0D_W3jtdBCAIht39FvPTtC6o9TywEQ_i1TL4BTDQIdndL1X2eoFawoczhWqhEQeP3MUs3XnLeBhtfbf25U3EsQ'
export const HEADER_PART = 'eyJhbGciOiJFUzI1NiIsImtpZCI6InRlc3Qta2lkIn0'
Expand Down
8 changes: 3 additions & 5 deletions packages/signature/src/lib/__test__/unit/verify.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { VerificationInput } from '../../types'
import { verify } from '../../verify'
import { ALGORITHM, DECODED_TOKEN, PUBLIC_KEY_PEM, REQUEST, SIGNED_TOKEN } from './mock'
import { DECODED_TOKEN, PUBLIC_KEY_PEM, SIGNED_TOKEN } from './mock'

describe('verify', () => {
it('verifies a request successfully', async () => {
const verificationInput: VerificationInput = {
request: REQUEST,
rawToken: SIGNED_TOKEN,
publicKey: PUBLIC_KEY_PEM,
algorithm: ALGORITHM
jwt: SIGNED_TOKEN,
publicKey: PUBLIC_KEY_PEM
}
const jwt = await verify(verificationInput)
expect(jwt).toEqual(DECODED_TOKEN)
Expand Down
2 changes: 1 addition & 1 deletion packages/signature/src/lib/hash-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const sort = (value: unknown): unknown => {
* @param value an object
* @returns object's hash
*/
export const hashRequest = (value: unknown): string => {
export const hash = (value: unknown): string => {
return createHash('sha256')
.update(stringify(sort(value)))
.digest('hex')
Expand Down
6 changes: 3 additions & 3 deletions packages/signature/src/lib/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SignJWT, base64url, importPKCS8 } from 'jose'
import { isHex, toBytes } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { JwtError } from './error'
import { hashRequest } from './hash-request'
import { hash } from './hash-request'
import { Alg, SignatureInput } from './types'

const DEF_EXP_TIME = '2h'
Expand All @@ -20,7 +20,7 @@ const eoaKeys = async (signingInput: SignatureInput): Promise<string> => {
const expNumeric = exp ? Math.floor(exp.getTime() / 1000) : now + 2 * 60 * 60
const header = { alg: algorithm, kid }
const payload = {
requestHash: hashRequest(request),
requestHash: hash(request),
iat: iatNumeric,
exp: expNumeric
}
Expand Down Expand Up @@ -49,7 +49,7 @@ export async function sign(signingInput: SignatureInput): Promise<string> {
return eoaKeys(signingInput)
}
const privateKey = await importPKCS8(pk, algorithm)
const requestHash = hashRequest(request)
const requestHash = hash(request)

const jwt = await new SignJWT({ requestHash })
.setProtectedHeader({ alg: algorithm, kid })
Expand Down
3 changes: 0 additions & 3 deletions packages/signature/src/lib/typeguards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,12 @@ function isDate(date: unknown): date is Date {
}
if (typeof date === 'string') {
const parsed = Date.parse(date)
console.log('### parsed', parsed)
return !isNaN(parsed)
}
if (typeof date === 'number') {
const parsed = new Date(date)
console.log('### parsed', parsed)
return !isNaN(parsed.getTime())
}
console.log('### date', date)
return false
}

Expand Down
5 changes: 1 addition & 4 deletions packages/signature/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,9 @@ export type SignatureInput = {
* Defines the input required to verify a JWT.
*
* @param {string} jwt - The JWT to be verified.
* @param {Request} request - The content of the request to be verified.
* @param {string} publicKey - The public key that corresponds to the private key used for signing.
*/
export type VerificationInput = {
rawToken: string
request: unknown
jwt: string
publicKey: string
algorithm: Alg
}
Loading

0 comments on commit f581e6b

Please sign in to comment.