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 <berend@animo.id>
  • Loading branch information
berendsliedrecht authored and TimoGlastra committed Aug 26, 2022
1 parent dad975d commit ae9cc32
Show file tree
Hide file tree
Showing 62 changed files with 825 additions and 149 deletions.
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
3 changes: 1 addition & 2 deletions packages/core/src/agent/EnvelopeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import type { EncryptedMessage, PlaintextMessage } from '../types'
import type { AgentMessage } from './AgentMessage'

import { InjectionSymbols } from '../constants'
import { KeyType } from '../crypto'
import { Key } from '../modules/dids'
import { Key, KeyType } from '../crypto'
import { ForwardMessage } from '../modules/routing/messages'
import { inject, injectable } from '../plugins'
import { Wallet } from '../wallet/Wallet'
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/agent/MessageSender.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Key } from '../crypto'
import type { ConnectionRecord } from '../modules/connections'
import type { DidDocument, Key } from '../modules/dids'
import type { DidDocument } from '../modules/dids'
import type { OutOfBandRecord } from '../modules/oob/repository'
import type { OutboundTransport } from '../transport/OutboundTransport'
import type { OutboundMessage, OutboundPackage, EncryptedMessage } from '../types'
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/agent/__tests__/MessageSender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import type { ResolvedDidCommService } from '../MessageSender'
import { TestMessage } from '../../../tests/TestMessage'
import { getAgentConfig, getMockConnection, mockFunction } from '../../../tests/helpers'
import testLogger from '../../../tests/logger'
import { KeyType } from '../../crypto'
import { Key, KeyType } from '../../crypto'
import { ReturnRouteTypes } from '../../decorators/transport/TransportDecorator'
import { Key, DidDocument, VerificationMethod } from '../../modules/dids'
import { DidDocument, VerificationMethod } from '../../modules/dids'
import { DidCommV1Service } from '../../modules/dids/domain/service/DidCommV1Service'
import { DidResolverService } from '../../modules/dids/services/DidResolverService'
import { InMemoryMessageRepository } from '../../storage/InMemoryMessageRepository'
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/agent/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Key } from '../crypto'
import type { ConnectionRecord } from '../modules/connections'
import type { Key } from '../modules/dids/domain/Key'
import type { OutOfBandRecord } from '../modules/oob/repository'
import type { OutboundMessage, OutboundServiceMessage } from '../types'
import type { AgentMessage } from './AgentMessage'
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/agent/models/InboundMessageContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Key } from '../../crypto'
import type { ConnectionRecord } from '../../modules/connections'
import type { Key } from '../../modules/dids'
import type { AgentMessage } from '../AgentMessage'

import { AriesFrameworkError } from '../../error'
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 @@ -8,6 +8,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 @@ -24,9 +27,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 @@ -37,7 +41,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 @@ -63,10 +67,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
10 changes: 5 additions & 5 deletions packages/core/src/modules/connections/ConnectionsModule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Key } from '../../crypto'
import type { DependencyManager } from '../../plugins'
import type { Key } from '../dids'
import type { OutOfBandRecord } from '../oob/repository'
import type { ConnectionRecord } from './repository/ConnectionRecord'
import type { Routing } from './services'
Expand All @@ -18,14 +18,14 @@ import { RoutingService } from '../routing/services/RoutingService'

import { DidExchangeProtocol } from './DidExchangeProtocol'
import {
AckMessageHandler,
ConnectionRequestHandler,
ConnectionResponseHandler,
AckMessageHandler,
TrustPingMessageHandler,
TrustPingResponseMessageHandler,
DidExchangeCompleteHandler,
DidExchangeRequestHandler,
DidExchangeResponseHandler,
DidExchangeCompleteHandler,
TrustPingMessageHandler,
TrustPingResponseMessageHandler,
} from './handlers'
import { HandshakeProtocol } from './models'
import { ConnectionRepository } from './repository'
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/modules/connections/DidExchangeProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import type { ConnectionRecord } from './repository'
import type { Routing } from './services/ConnectionService'

import { AgentConfig } from '../../agent/AgentConfig'
import { KeyType } from '../../crypto'
import { Key, KeyType } from '../../crypto'
import { JwsService } from '../../crypto/JwsService'
import { Attachment, AttachmentData } from '../../decorators/attachment/Attachment'
import { AriesFrameworkError } from '../../error'
import { injectable } from '../../plugins'
import { JsonEncoder } from '../../utils/JsonEncoder'
import { JsonTransformer } from '../../utils/JsonTransformer'
import { DidDocument, Key } from '../dids'
import { DidDocument } from '../dids'
import { DidDocumentRole } from '../dids/domain/DidDocumentRole'
import { createDidDocumentFromServices } from '../dids/domain/createPeerDidFromServices'
import { getKeyDidMappingByVerificationMethod } from '../dids/domain/key-type'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { getAgentConfig, getMockConnection, getMockOutOfBand, mockFunction } fro
import { AgentMessage } from '../../../agent/AgentMessage'
import { EventEmitter } from '../../../agent/EventEmitter'
import { InboundMessageContext } from '../../../agent/models/InboundMessageContext'
import { KeyType } from '../../../crypto'
import { Key, KeyType } from '../../../crypto'
import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils'
import { JsonTransformer } from '../../../utils/JsonTransformer'
import { uuid } from '../../../utils/uuid'
import { IndyWallet } from '../../../wallet/IndyWallet'
import { AckMessage, AckStatus } from '../../common'
import { DidKey, IndyAgentService, Key } from '../../dids'
import { DidKey, IndyAgentService } from '../../dids'
import { DidCommV1Service } from '../../dids/domain/service/DidCommV1Service'
import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1'
import { DidRepository } from '../../dids/repository'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import { first, map, timeout } from 'rxjs/operators'
import { AgentConfig } from '../../../agent/AgentConfig'
import { EventEmitter } from '../../../agent/EventEmitter'
import { InjectionSymbols } from '../../../constants'
import { Key } from '../../../crypto'
import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators/signature/SignatureDecoratorUtils'
import { AriesFrameworkError } from '../../../error'
import { inject, injectable } from '../../../plugins'
import { JsonTransformer } from '../../../utils/JsonTransformer'
import { indyDidFromPublicKeyBase58 } from '../../../utils/did'
import { Wallet } from '../../../wallet/Wallet'
import { DidKey, Key, IndyAgentService } from '../../dids'
import { DidKey, IndyAgentService } from '../../dids'
import { DidDocumentRole } from '../../dids/domain/DidDocumentRole'
import { didKeyToVerkey } from '../../dids/helpers'
import { didDocumentJsonToNumAlgo1Did } from '../../dids/methods/peer/peerDidNumAlgo1'
Expand Down
Loading

0 comments on commit ae9cc32

Please sign in to comment.