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

Frankie/milestone#signature validation #445

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Version header format: `[version] Name - year-month-day (entropy-core compatibil
### Changed

### Broke

- sign and sign with adapters no longer returns just the signature. It now returns a object `SignatureData` that contains the signature and information pronating to it see the `SignatureData` interface in (./src/signing/index.ts)(./src/signing/index.ts)
- `entropy.sign` and `entropy.signWithAdaptersInOrder` function argument interface key renamed `sigRequestHash` -> `hexMessage`
### Dev

### Meta
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ const verifyingKey = await entropy.getVerifyingKey(address)
// signing manager

await entropy.signingManager.sign({
sigRequestHash,
hexMessage,
hash: this.adapters[type].hash,
type,
})
Expand Down Expand Up @@ -469,7 +469,7 @@ Will throw an error if the transaction type does not have a corresponding adapte
// signing manager

await entropy.signingManager.sign({
sigRequestHash,
hexMessage,
hash: this.adapters[type].hash,
type,
})
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"@types/lodash.clonedeep": "^4.5.9",
"@types/node": "^20.12.12",
"debug": "^4.3.4",
"ethers": "^6.13.4",
"hpke-js": "^1.2.7",
"lodash.clonedeep": "^4.5.0",
"uuid": "^9.0.1",
Expand Down
23 changes: 19 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { ApiPromise } from '@polkadot/api'
import xtend from 'xtend'
import { createSubstrate, isValidSubstrateAddress as isDeployer } from './utils'
import RegistrationManager, { RegistrationParams } from './registration'
import SignatureRequestManager, { SigOps, SigWithAdaptersOps } from './signing'
import { crypto, loadCryptoLib } from './utils/crypto'
import SignatureRequestManager, { SigOps, SigWithAdaptersOps, SignatureData, AdaptedSignatureData } from './signing'
import { crypto, loadCryptoLib, verify } from './utils/crypto'
import { Adapter } from './signing/adapters/types'
import ProgramManager from './programs'
import Keyring from './keys'
Expand Down Expand Up @@ -199,14 +199,29 @@ export default class Entropy {
* It returns the signature from the first validator after validation.
*
* @param {SigOps} params - The signature operation parameters.
* @returns {Promise<Uint8Array>} A promise that resolves to the signed hash as a Uint8Array.
* @returns {Promise<SignatureData>} A promise that resolves to the signed hash as a Uint8Array.
* @throws {Error} If there's an error in the signing routine.
*/

async sign (params: SigOps): Promise<Uint8Array> {
async sign (params: SigOps): Promise<SignatureData> {
await this.ready
return this.signingManager.sign(params)
}
/**
* Verifies signature data.
* This method involves various steps including validator selection, transaction request formatting,
* and submission of these requests to validators for signing.
* It returns the signature from the first validator after validation.
*
* @param {SignatureData | AdaptedSignatureData} params - The signature operation parameters.
* @returns {Promise<Uint8Array>} A promise that resolves to the signed hash as a Uint8Array.
* @throws {Error} If there's an error in the verifying routine.
*/

async verify (params: SignatureData | AdaptedSignatureData): Promise<boolean> {
await this.ready
return verify(params)
}

/**
* Shuts the Entropy SDK down gracefully.
Expand Down
6 changes: 3 additions & 3 deletions src/signing/adapters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const PROGRAM_INTERFACE = {
export const ADAPTER_PROGRAMS = [PROGRAM_INTERFACE]

export interface PreSignResult extends PRESIGN_RESULT {
sigRequestHash: HexString
hexMessage: HexString
auxilary_data: [AuxData]
}

Expand All @@ -52,9 +52,9 @@ export async function preSign(
const stringMessage = JSON.stringify(message)
// un comment for device key signature:
// const signedMessage = deviceKey.pair.sign(stringMessage)
const sigRequestHash = toHex(stringMessage)
const hexMessage = toHex(stringMessage)

return { sigRequestHash, auxilary_data: PROGRAM_INTERFACE.auxilary_data }
return { hexMessage, auxilary_data: PROGRAM_INTERFACE.auxilary_data }
}

export const type = 'noop'
Expand Down
6 changes: 3 additions & 3 deletions src/signing/adapters/device-key-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const DEVICE_KEY_PROXY_PROGRAM_INTERFACE = {
// export const ADAPTER_PROGRAMS = [DEVICE_KEY_PROXY_PROGRAM_INTERFACE]

export interface PreSignResult extends PRESIGN_RESULT {
sigRequestHash: HexString
hexMessage: HexString
auxilary_data: [AuxData]
}

Expand All @@ -57,7 +57,7 @@ export async function preSign (
): Promise<PreSignResult> {
const stringMessage = JSON.stringify(message)
const signedMessage = deviceKey.pair.sign(stringMessage)
const sigRequestHash = toHex(stringMessage)
const hexMessage = toHex(stringMessage)

const convertedSig = btoa(String.fromCharCode.apply(null, signedMessage))
// Base64 encoded string
Expand All @@ -75,7 +75,7 @@ export async function preSign (
},
]

return { sigRequestHash, auxilary_data }
return { hexMessage, auxilary_data }
}

export const type = 'deviceKeyProxy'
Expand Down
6 changes: 3 additions & 3 deletions src/signing/adapters/noop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const PROGRAM_INTERFACE = {
export const ADAPTER_PROGRAMS = [PROGRAM_INTERFACE]

export interface PreSignResult extends PRESIGN_RESULT {
sigRequestHash: HexString
hexMessage: HexString
auxilary_data: NoopAuxData[]
}

Expand All @@ -37,9 +37,9 @@ export async function preSign (
const stringMessage = JSON.stringify(message)
// un comment for device key signature:
// const signedMessage = deviceKey.pair.sign(stringMessage)
const sigRequestHash = toHex(stringMessage)
const hexMessage = toHex(stringMessage)

return { sigRequestHash, auxilary_data: PROGRAM_INTERFACE.auxilary_data }
return { hexMessage, auxilary_data: PROGRAM_INTERFACE.auxilary_data }
}

export const type = 'noop'
Expand Down
2 changes: 1 addition & 1 deletion src/signing/adapters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface AUX_DATA {
* and auxiliary data.
*/
export interface PRESIGN_RESULT {
sigRequestHash: HexString
hexMessage: HexString
auxilary_data: AUX_DATA[] | unknown[]
}

Expand Down
57 changes: 38 additions & 19 deletions src/signing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,32 @@ import { Signer } from '../keys/types/internal'
import { defaultAdapters } from './adapters/default'
import { Adapter } from './adapters/types'
import { ValidatorInfo } from '../types/internal'
import { stripHexPrefix, sendHttpPost, toHex, } from '../utils'
import { stripHexPrefix, addHexPrefix, sendHttpPost, toHex, } from '../utils'
import { crypto } from '../utils/crypto'
import { CryptoLib } from '../utils/crypto/types'
import Keyring from '../keys'

/**
* Represents a signed message and the associated data.
*/
export interface SignatureData {
signature: string
verifyingKey: string
hashType: string
message: string // hex string as bytes?
}

//preemptive typing for adapters
export interface AdaptedSignatureData extends SignatureData {
messageParams?: any // the original object or message
}
/**
* Represents an encrypted message for transaction requests.
*/
export interface EncMsg {
msg: string
url: string
tss_account: string
// signature_verifying_key: number[]
}


Expand All @@ -42,7 +55,7 @@ export interface SigWithAdaptersOps extends SigMsgOps {
}

export interface SigOps {
sigRequestHash: string
hexMessage: string
hash: string
type?: string
auxiliaryData?: unknown[]
Expand Down Expand Up @@ -153,7 +166,7 @@ export default class SignatureRequestManager {
}
return agg
}, [])
// this is the named keys we care about from post sign. { sigRequestHash, auxilary_data }
// this is the named keys we care about from post sign. { hexMessage, auxilary_data }
const results = await Promise.all(
adaptersToRun.map((adapter) => {
return adapter.preSign(this.signer, msg)
Expand All @@ -166,10 +179,10 @@ export default class SignatureRequestManager {
})
// flatten
const auxiliaryData = [].concat(...auxiliaryDataCollection)
// grab the first sigRequestHash
const { sigRequestHash } = results[0]
// grab the first hexMessage
const { hexMessage } = results[0]
const signature = await this.sign({
sigRequestHash,
hexMessage,
hash: adaptersToRun[0].HASHING_ALGORITHM,
auxiliaryData,
signatureVerifyingKey,
Expand All @@ -181,20 +194,20 @@ export default class SignatureRequestManager {
* Signs a given signature request hash.
*
* @param {SigOps} sigOps - Parameters for the signature operation.
* @param {string} sigOps.sigRequestHash - The hash of the signature request to be signed.
* @param {string} sigOps.hexMessage - The hash of the signature request to be signed.
* @param {string} sigOps.hash - The name of the hashing algorithm used to hash the signature.
* @param {unknown[]} sigOps.auxilaryData - Additional data for the signature operation.
* @param {signatureVerifyingKey} sigOps.signatureVerifyingKey - The verifying key for the signature requested
* @returns {Promise<Uint8Array>} A promise resolving to the signed hash as a Uint8Array.
*/

async sign ({
sigRequestHash,
hexMessage,
hash,
auxiliaryData,
signatureVerifyingKey: signatureVerifyingKeyOverwrite,
}: SigOps): Promise<Uint8Array> {
const strippedsigRequestHash = stripHexPrefix(sigRequestHash)
}: SigOps): Promise<SignatureData> {
const strippedHexMessage = stripHexPrefix(hexMessage)
// @ts-ignore: next line
const validators: string[] = (await this.substrate.query.session.validators()).toHuman()
// @ts-ignore: next line
Expand All @@ -210,7 +223,7 @@ export default class SignatureRequestManager {
const signatureVerifyingKey = signatureVerifyingKeyOverwrite || this.verifyingKey

const txRequest = {
strippedsigRequestHash,
strippedHexMessage,
auxiliaryData,
validator: validatorInfo,
hash,
Expand All @@ -219,8 +232,14 @@ export default class SignatureRequestManager {

const message: EncMsg = await this.formatTxRequest(txRequest)
const sigs = await this.submitTransactionRequest(message)
const sig = await this.#verifyAndPick(sigs, signingCommittee)
return Uint8Array.from(atob(sig), (c) => c.charCodeAt(0))
const base64Sig = await this.#verifyAndPick(sigs, signingCommittee)
const buffSig = Buffer.from(base64Sig, 'base64')
return {
signature: addHexPrefix(buffSig.toString('hex')),
hashType: hash,
verifyingKey: signatureVerifyingKey,
message: addHexPrefix(hexMessage),
}
}

/**
Expand All @@ -239,7 +258,7 @@ export default class SignatureRequestManager {
* Generates transaction requests formatted for validators.
*
* @param {object} params - Parameters for generating the transaction request.
* @param {string} params.strippedsigRequestHash - Stripped signature request hash.
* @param {string} params.strippedHexMessage - Stripped signature request hash.
* @param {unknown[]} [params.auxiliaryData] - Additional data for the transaction request.
* @param {ValidatorInfo[]} params.validatorsInfo - Information about the validators.
* @param {string} [params.hash] - The hash type.
Expand All @@ -248,20 +267,20 @@ export default class SignatureRequestManager {
*/

async formatTxRequest ({
strippedsigRequestHash,
strippedHexMessage,
auxiliaryData,
validator,
hash,
signatureVerifyingKey,
}: {
strippedsigRequestHash: string
strippedHexMessage: string
auxiliaryData?: unknown[]
validator: ValidatorInfo
hash?: string
signatureVerifyingKey: string
}): Promise<EncMsg> {
const txRequestData: UserSignatureRequest = {
message: stripHexPrefix(strippedsigRequestHash),
message: stripHexPrefix(strippedHexMessage),
auxilary_data: auxiliaryData,
block_number: await this.getBlockNumber(),
hash,
Expand Down Expand Up @@ -318,7 +337,7 @@ export default class SignatureRequestManager {
/**
* Selects validators based on the signature request.
*
* @param {string} sigRequest - The signature request hash.
* @param {string} hexMessage - The signature request hash.
* @returns {Promise<ValidatorInfo[]>} A promise resolving to an array of validator information.
*/

Expand Down
34 changes: 33 additions & 1 deletion src/utils/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {
cryptoWaitReady,
decodeAddress,
encodeAddress,
signatureVerify,
blake2AsHex,
} from '@polkadot/util-crypto'

import { keccak256, SigningKey } from 'ethers'
import * as polkadotCryptoUtil from '@polkadot/util-crypto'
import { CryptoLib, ResObjectType } from './types'
import { u8aToHex } from '@polkadot/util'
import { SignatureData, AdaptedSignatureData } from '../../signing'

let cryptoLib
const res: ResObjectType = {
Expand Down Expand Up @@ -97,3 +100,32 @@ async function verifySignature (

return signatureVerify(message, signature, hexPublicKey).isValid
}

/**
* Verifies the signature of a of a signed message coming from the signing method in the entropy class.
*
* @param {@link SignatureData} - The signature data to be verified.
* @returns A Promise that resolves to a boolean indicating whether the signature is valid.
*/

export async function verify (sigData: SignatureData | AdaptedSignatureData): Promise<boolean> {
const { hashType, message, verifyingKey, signature } = sigData

if (!hashType) throw new Error('hashType not include in the signature data')
if (!message) throw new Error('message not include in the signature data')
if (!verifyingKey) throw new Error('verifyingKey not include in the signature data')
if (!signature) throw new Error('signature not include in the signature data')


if (hashType.toLowerCase() === 'keccak') {
const recoveredPk = SigningKey.recoverPublicKey(keccak256(message), signature)
return recoveredPk === SigningKey.computePublicKey(verifyingKey)
}

if (hashType.toLowerCase() === 'blake2_256') {
const address = encodeAddress(blake2AsHex(verifyingKey))
return signatureVerify(message, signature, address).isValid
}

throw new Error(`unsupported hashType: ${hashType}`)
}
15 changes: 15 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ export function stripHexPrefix (str: string): string {
return str
}

/**
* Removes the '0x' prefix from a hexadecimal string if it exists.
*
* @param {string} str - The hexadecimal string.
* @returns {string} The string without the '0x' prefix.
*/

export function addHexPrefix (str: string): string {
if (typeof str === 'string') {
if (str.startsWith('0x')) return str
return `0x${str}`
}
throw new TypeError('not a string')
}

/**
* Validates whether a given address is a valid Substrate address.
*
Expand Down
Loading
Loading