Skip to content

Commit

Permalink
feat: bbs createKey, sign and verify (#684)
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
berendsliedrecht authored and karimStekelenburg committed May 12, 2022
1 parent cbdff28 commit 113cbb9
Show file tree
Hide file tree
Showing 39 changed files with 789 additions and 90 deletions.
5 changes: 5 additions & 0 deletions docs/setup-electron.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ To start using Electron, the prerequisites of NodeJS are required. Please follow
To add the aries framework and indy to your project execute the following:

## Installing dependencies

```sh
yarn add @aries-framework/core @aries-framework/node indy-sdk

# Additional for typescript
yarn add --dev @types/indy-sdk
```

Right now, as a patch that will later be changed, some platforms will have an "error" when installing the dependencies. This is because the BBS signatures library that we use is built for Linux x86 and MacOS x86 (and not Windows and MacOS arm). This means that it will show that it could not download the binary.
This is not an error, as the library that fails is `node-bbs-signaturs` and is an optional dependency for perfomance improvements. It will fallback to a, slower, wasm build.

Because Electron is like a browser-environment, some additional work has to be done to get it working. The indy-sdk is used to make calls to `libindy`. Since `libindy` is not build for browser environments, a binding for the indy-sdk has to be created from the browser to the NodeJS environment in the `public/preload.js` file.

```ts
Expand Down
5 changes: 5 additions & 0 deletions docs/setup-nodejs.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ To start using Aries Framework JavaScript in NodeJS some platform specific depen
- [Windows](../docs/libindy/windows.md)
3. Add `@aries-framework/core` and `@aries-framework/node` to your project.

## Installing dependencies

```bash
yarn add @aries-framework/core @aries-framework/node
```

Right now, as a patch that will later be changed, some platforms will have an "error" when installing the dependencies. This is because the BBS signatures library that we use is built for Linux x86 and MacOS x86 (and not Windows and MacOS arm). This means that it will show that it could not download the binary.
This is not an error, as the library that fails is `node-bbs-signaturs` and is an optional dependency for perfomance improvements. It will fallback to a, slower, wasm build.

## Agent Setup

Initializing the Agent also requires some NodeJS specific setup, mainly for the Indy SDK and File System. Below is a sample config, see the [README](../README.md#getting-started) for an overview of getting started guides. If you want to jump right in, check the [Getting Started: Agent](./getting-started/0-agent.md) guide.
Expand Down
41 changes: 41 additions & 0 deletions docs/setup-react-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ To start using Aries Framework JavaScript in React Native some platform specific
1. Follow the [React Native Setup](https://reactnative.dev/docs/environment-setup) guide to set up your environment.
2. Add `@aries-framework/core`, `@aries-framework/react-native`, `react-native-fs`, and `react-native-get-random-values` to your project.

## Installing dependencies

```bash
yarn add @aries-framework/core @aries-framework/react-native react-native-fs react-native-get-random-values
```

Right now, as a patch that will later be changed, some platforms will have an "error" when installing the dependencies. This is because the BBS signatures library that we use is built for Linux x86 and MacOS x86 (and not Windows and MacOS arm). This means that it will show that it could not download the binary.
This is not an error, as the library that fails is `node-bbs-signaturs` and is an optional dependency for perfomance improvements. It will fallback to a, slower, wasm build.

3. Install [Libindy](https://github.com/hyperledger/indy-sdk) for iOS and Android:

- [iOS](../docs/libindy/ios.md)
Expand Down Expand Up @@ -81,3 +86,39 @@ try {
console.log(error)
}
```

## Using BBS Signatures

When using AFJ inside the React Native environment, temporarily, a dependency for creating keys, sigining and verifying
with bbs keys must be swapped. Inside your package.json the following must be added:

#### yarn

```diff
+ "resolutions": {
+ "@mattrglobal/bbs-signatures": "@animo-id/[email protected]",
+ },
"dependencies": {
...
+ "@animo-id/react-native-bbs-signatures": "0.1.0",
}
```

#### npm

```diff
+ "overrides": {
+ "@mattrglobal/bbs-signatures": "@animo-id/[email protected]",
+ },
"dependencies": {
...
+ "@animo-id/react-native-bbs-signatures": "0.1.0",
}
```

The resolution field says that any instance of `@mattrglobal/bbs-signatures` in any child dependency must be swapped
with `react-native-bbs-signatures`.

The added dependency is required for autolinking and should be the same as the one used in the resolution.

[React Native Bbs Signature](https://github.com/animo/react-native-bbs-signatures) has some quirks with setting it up correctly. If any errors occur while using this library, please refer to their README for the installation guide.
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"prepublishOnly": "yarn run build"
},
"dependencies": {
"@mattrglobal/bbs-signatures": "^1.0.0",
"@multiformats/base-x": "^4.0.1",
"@stablelib/ed25519": "^1.0.2",
"@stablelib/sha256": "^1.0.1",
Expand Down
151 changes: 151 additions & 0 deletions packages/core/src/crypto/BbsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import type { CreateKeyOptions } from '../wallet'
import type { BlsKeyPair as _BlsKeyPair } from '@mattrglobal/bbs-signatures'

import {
bls12381toBbs,
generateBls12381G2KeyPair,
generateBls12381G1KeyPair,
sign,
verify,
} from '@mattrglobal/bbs-signatures'

import { TypedArrayEncoder } from '../utils/TypedArrayEncoder'
import { Buffer } from '../utils/buffer'
import { WalletError } from '../wallet/error'

import { KeyType } from './KeyType'

export interface BlsKeyPair {
publicKeyBase58: string
privateKeyBase58: string
keyType: Extract<KeyType, KeyType.Bls12381g1 | KeyType.Bls12381g2 | KeyType.Bls12381g1g2>
}

interface BbsCreateKeyOptions extends CreateKeyOptions {
keyType: Extract<KeyType, KeyType.Bls12381g1 | KeyType.Bls12381g2>
}

interface BbsSignOptions {
messages: Buffer | Buffer[]
publicKey: Buffer
privateKey: Buffer
}

interface BbsVerifyOptions {
publicKey: Buffer
signature: Buffer
messages: Buffer | Buffer[]
}

export class BbsService {
/**
* Create an instance of a Key class for the following key types:
* - Bls12381g1
* - Bls12381g2
*
* @param keyType KeyType The type of key to be created (see above for the accepted types)
*
* @returns A Key class with the public key and key type
*
* @throws {WalletError} When a key could not be created
* @throws {WalletError} When the method is called with an invalid keytype
*/
public static async createKey({ keyType, seed }: BbsCreateKeyOptions): Promise<BlsKeyPair> {
// Generate bytes from the seed as required by the bbs-signatures libraries
const seedBytes = seed ? TypedArrayEncoder.fromString(seed) : undefined

// Temporary keypair holder
let blsKeyPair: Required<_BlsKeyPair>

switch (keyType) {
case KeyType.Bls12381g1:
// Generate a bls12-381G1 keypair
blsKeyPair = await generateBls12381G1KeyPair(seedBytes)
break
case KeyType.Bls12381g2:
// Generate a bls12-381G2 keypair
blsKeyPair = await generateBls12381G2KeyPair(seedBytes)
break
default:
// additional check. Should never be hit as this function will only be called from a place where
// a key type check already happened.
throw new WalletError(`Cannot create key with the BbsService for key type: ${keyType}`)
}

return {
keyType,
publicKeyBase58: TypedArrayEncoder.toBase58(blsKeyPair.publicKey),
privateKeyBase58: TypedArrayEncoder.toBase58(blsKeyPair.secretKey),
}
}

/**
* Sign an arbitrary amount of messages, in byte form, with a keypair
*
* @param messages Buffer[] List of messages in Buffer form
* @param publicKey Buffer Publickey required for the signing process
* @param privateKey Buffer PrivateKey required for the signing process
*
* @returns A Buffer containing the signature of the messages
*
* @throws {WalletError} When there are no supplied messages
*/
public static async sign({ messages, publicKey, privateKey }: BbsSignOptions): Promise<Buffer> {
if (messages.length === 0) throw new WalletError('Unable to create a signature without any messages')
// Check if it is a single message or list and if it is a single message convert it to a list
const normalizedMessages = (TypedArrayEncoder.isTypedArray(messages) ? [messages as Buffer] : messages) as Buffer[]

// Get the Uint8Array variant of all the messages
const messageBuffers = normalizedMessages.map((m) => Uint8Array.from(m))

const bbsKeyPair = await bls12381toBbs({
keyPair: { publicKey: Uint8Array.from(publicKey), secretKey: Uint8Array.from(privateKey) },
messageCount: normalizedMessages.length,
})

// Sign the messages via the keyPair
const signature = await sign({
keyPair: bbsKeyPair,
messages: messageBuffers,
})

// Convert the Uint8Array signature to a Buffer type
return Buffer.from(signature)
}

/**
* Verify an arbitrary amount of messages with their signature created with their key pair
*
* @param publicKey Buffer The public key used to sign the messages
* @param messages Buffer[] The messages that have to be verified if they are signed
* @param signature Buffer The signature that has to be verified if it was created with the messages and public key
*
* @returns A boolean whether the signature is create with the public key over the messages
*
* @throws {WalletError} When the message list is empty
* @throws {WalletError} When the verification process failed
*/
public static async verify({ signature, messages, publicKey }: BbsVerifyOptions): Promise<boolean> {
if (messages.length === 0) throw new WalletError('Unable to create a signature without any messages')
// Check if it is a single message or list and if it is a single message convert it to a list
const normalizedMessages = (TypedArrayEncoder.isTypedArray(messages) ? [messages as Buffer] : messages) as Buffer[]

// Get the Uint8Array variant of all the messages
const messageBuffers = normalizedMessages.map((m) => Uint8Array.from(m))

const bbsKeyPair = await bls12381toBbs({
keyPair: { publicKey: Uint8Array.from(publicKey) },
messageCount: normalizedMessages.length,
})

// Verify the signature against the messages with their public key
const { verified, error } = await verify({ signature, messages: messageBuffers, publicKey: bbsKeyPair.publicKey })

// If the messages could not be verified and an error occured
if (!verified && error) {
throw new WalletError(`Could not verify the signature against the messages: ${error}`)
}

return verified
}
}
11 changes: 8 additions & 3 deletions packages/core/src/crypto/JwsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { JsonEncoder, TypedArrayEncoder } from '../utils'
import { Wallet } from '../wallet'
import { WalletError } from '../wallet/error'

import { Key } from './Key'
import { KeyType } from './KeyType'

// TODO: support more key types, more generic jws format
const JWS_KEY_TYPE = 'OKP'
const JWS_CURVE = 'Ed25519'
Expand All @@ -25,9 +28,10 @@ export class JwsService {
public async createJws({ payload, verkey, header }: CreateJwsOptions): Promise<JwsGeneralFormat> {
const base64Payload = TypedArrayEncoder.toBase64URL(payload)
const base64Protected = JsonEncoder.toBase64URL(this.buildProtected(verkey))
const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519)

const signature = TypedArrayEncoder.toBase64URL(
await this.wallet.sign(TypedArrayEncoder.fromString(`${base64Protected}.${base64Payload}`), verkey)
await this.wallet.sign({ data: TypedArrayEncoder.fromString(`${base64Protected}.${base64Payload}`), key })
)

return {
Expand All @@ -38,7 +42,7 @@ export class JwsService {
}

/**
* Verify a a JWS
* Verify a JWS
*/
public async verifyJws({ jws, payload }: VerifyJwsOptions): Promise<VerifyJwsResult> {
const base64Payload = TypedArrayEncoder.toBase64URL(payload)
Expand All @@ -64,10 +68,11 @@ export class JwsService {
const signature = TypedArrayEncoder.fromBase64(jws.signature)

const verkey = TypedArrayEncoder.toBase58(TypedArrayEncoder.fromBase64(protectedJson?.jwk?.x))
const key = Key.fromPublicKeyBase58(verkey, KeyType.Ed25519)
signerVerkeys.push(verkey)

try {
const isValid = await this.wallet.verify(verkey, data, signature)
const isValid = await this.wallet.verify({ key, data, signature })

if (!isValid) {
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { KeyType } from '../../../crypto'
import type { KeyType } from './KeyType'

import { Buffer, TypedArrayEncoder, MultiBaseEncoder, VarintEncoder } from '../../../utils'
import { Buffer, MultiBaseEncoder, TypedArrayEncoder, VarintEncoder } from '../utils'

import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './key-type/multiCodecKey'
import { getKeyTypeByMultiCodecPrefix, getMultiCodecPrefixByKeytype } from './multiCodecKey'

export class Key {
public readonly publicKey: Buffer
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/crypto/__tests__/JwsService.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Wallet } from '@aries-framework/core'

import { getAgentConfig } from '../../../tests/helpers'
import { DidKey, Key } from '../../modules/dids'
import { DidKey } from '../../modules/dids'
import { Buffer, JsonEncoder } from '../../utils'
import { IndyWallet } from '../../wallet/IndyWallet'
import { JwsService } from '../JwsService'
import { Key } from '../Key'
import { KeyType } from '../KeyType'

import * as didJwsz6Mkf from './__fixtures__/didJwsz6Mkf'
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { KeyType } from './KeyType'
export { Key } from './Key'
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { KeyType } from '../../../../crypto'
import { KeyType } from './KeyType'

// based on https://github.com/multiformats/multicodec/blob/master/table.csv
const multiCodecPrefixMap: Record<string, KeyType> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Wallet } from '../../wallet/Wallet'

import { Key, KeyType } from '../../crypto'
import { AriesFrameworkError } from '../../error'
import { JsonEncoder } from '../../utils/JsonEncoder'
import { TypedArrayEncoder } from '../../utils/TypedArrayEncoder'
Expand All @@ -21,12 +22,14 @@ export async function unpackAndVerifySignatureDecorator(
wallet: Wallet
): Promise<Record<string, unknown>> {
const signerVerkey = decorator.signer
const key = Key.fromPublicKeyBase58(signerVerkey, KeyType.Ed25519)

// first 8 bytes are for 64 bit integer from unix epoch
const signedData = TypedArrayEncoder.fromBase64(decorator.signatureData)
const signature = TypedArrayEncoder.fromBase64(decorator.signature)

const isValid = await wallet.verify(signerVerkey, signedData, signature)
// const isValid = await wallet.verify(signerVerkey, signedData, signature)
const isValid = await wallet.verify({ signature, data: signedData, key })

if (!isValid) {
throw new AriesFrameworkError('Signature is not valid')
Expand All @@ -47,8 +50,9 @@ export async function unpackAndVerifySignatureDecorator(
*/
export async function signData(data: unknown, wallet: Wallet, signerKey: string): Promise<SignatureDecorator> {
const dataBuffer = Buffer.concat([timestamp(), JsonEncoder.toBuffer(data)])
const key = Key.fromPublicKeyBase58(signerKey, KeyType.Ed25519)

const signatureBuffer = await wallet.sign(dataBuffer, signerKey)
const signatureBuffer = await wallet.sign({ key, data: dataBuffer })

const signatureDecorator = new SignatureDecorator({
signatureType: 'https://didcomm.org/signature/1.0/ed25519Sha512_single',
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/modules/dids/__tests__/peer-did.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { IndyLedgerService } from '../../ledger'

import { getAgentConfig } from '../../../../tests/helpers'
import { KeyType } from '../../../crypto'
import { Key, KeyType } from '../../../crypto'
import { IndyStorageService } from '../../../storage/IndyStorageService'
import { JsonTransformer } from '../../../utils'
import { IndyWallet } from '../../../wallet/IndyWallet'
import { DidCommService, DidDocument, DidDocumentBuilder, Key } from '../domain'
import { DidCommService, DidDocument, DidDocumentBuilder } from '../domain'
import { DidDocumentRole } from '../domain/DidDocumentRole'
import { convertPublicKeyToX25519, getEd25519VerificationMethod } from '../domain/key-type/ed25519'
import { getX25519VerificationMethod } from '../domain/key-type/x25519'
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/modules/dids/domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export * from './service'
export * from './verificationMethod'
export * from './DidDocument'
export * from './DidDocumentBuilder'
export * from './Key'
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { KeyType } from '../../../../../crypto'
import { JsonTransformer, TypedArrayEncoder, Buffer } from '../../../../../utils'
import { Key } from '../../../../../crypto/Key'
import { Buffer, JsonTransformer, TypedArrayEncoder } from '../../../../../utils'
import keyBls12381g1Fixture from '../../../__tests__/__fixtures__/didKeyBls12381g1.json'
import { Key } from '../../Key'
import { VerificationMethod } from '../../verificationMethod'
import { keyDidBls12381g1 } from '../bls12381g1'

Expand Down
Loading

0 comments on commit 113cbb9

Please sign in to comment.