Skip to content

Commit

Permalink
[NayNay] Verify signatures
Browse files Browse the repository at this point in the history
- added new verify command to verify signatures
- updated result of sign command to be stringified json making it easier to utilize verify command immediately after signing
- added tests
  • Loading branch information
rh0delta committed Jan 8, 2025
1 parent 085620f commit ced0f47
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 122 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"homepage": "https://github.com/entropyxyz/cli#readme",
"dependencies": {
"@entropyxyz/sdk": "0.4.0",
"@entropyxyz/sdk": "0.4.1-2",
"ajv": "^8.17.1",
"commander": "^12.1.0",
"env-paths": "^3.0.0",
Expand Down
2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { entropyTransferCommand as transfer } from './transfer/command'
import { entropySignCommand as sign } from './sign/command'
import { entropyBalanceCommand as balance } from './balance/command'
import { entropyProgramCommand as program } from './program/command'
import { entropyVerifyCommand as verify } from './verify/command'

const packageVersion = 'v' + require('../package.json').version
const coreVersion = process.env.ENTROPY_CORE_VERSION.split('-')[1]
Expand All @@ -29,6 +30,7 @@ cli
.addCommand(balance())
.addCommand(transfer())
.addCommand(program())
.addCommand(verify())

.option('-v, --version', 'Displays the current running version of Entropy CLI')
.option('-cv, --core-version', 'Displays the current running version of the Entropy Protocol')
Expand Down
2 changes: 1 addition & 1 deletion src/common/load-entropy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ async function loadEntropy (opts: LoadEntropyOpts): Promise<Entropy> {
const storedConfig = await opts.config.get()
if (!storedConfig) throw Error('no config!!') // TEMP: want to see if we hit this!

let account = resolveAccount(storedConfig, opts.account)
let account = resolveAccount(storedConfig, opts.account || storedConfig.selectedAccount)
const endpoint = resolveEndpoint(storedConfig, opts.endpoint)
// NOTE: while it would be nice to parse opts --account, --endpoint with Commander
// the argParser for these Options does not have access to the --config option,
Expand Down
10 changes: 5 additions & 5 deletions src/faucet/helpers/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Registry, SignerPayloadJSON } from "@polkadot/types/types";
import { u8aToHex } from "@polkadot/util";
import { blake2AsHex, decodeAddress, encodeAddress, signatureVerify } from "@polkadot/util-crypto";
import { stripHexPrefix } from "../../common/utils";
import { HexString } from "@polkadot/util/types";

let id = 0
export default class FaucetSigner implements Signer {
Expand Down Expand Up @@ -40,18 +41,17 @@ export default class FaucetSigner implements Signer {
amount: this.amount
}

const signature = await this.#entropy.sign({
sigRequestHash: u8aToHex(raw),
const { signature } = await this.#entropy.sign({
hexMessage: u8aToHex(raw),
// @ts-ignore
hash: { custom: 0 }, // NOTE: this is the custom hashing algo used for the faucet program.
auxiliaryData: [auxData],
signatureVerifyingKey: this.chosenVerifyingKey
})

let sigHex = u8aToHex(signature);
// the 02 prefix is needed for signature type edcsa (00 = ed25519, 01 = sr25519, 02 = ecdsa)
// ref: https://github.com/polkadot-js/tools/issues/175#issuecomment-767496439
sigHex = `0x02${stripHexPrefix(sigHex)}`
const sigHex = `0x02${stripHexPrefix(signature)}`

const hashedKey = blake2AsHex(this.chosenVerifyingKey)
const faucetAddress = encodeAddress(hashedKey)
Expand All @@ -62,7 +62,7 @@ export default class FaucetSigner implements Signer {
const signatureValidation = signatureVerify(u8aToHex(raw), sigHex, hexPublicKey)

if (signatureValidation.isValid) {
return { id: id++, signature: sigHex }
return { id: id++, signature: sigHex as HexString }
} else {
throw new Error('FaucetSignerError: Signature is not valid')
}
Expand Down
18 changes: 4 additions & 14 deletions src/sign/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Command, /* Option */ } from 'commander'
import { EntropySign } from './main'
import { accountOption, configOption, endpointOption, cliWrite } from '../common/utils-cli'
import { loadEntropyCli } from '../common/load-entropy'
import { stringify } from 'src/common/utils'

export function entropySignCommand () {
const signCommand = new Command('sign')
Expand All @@ -11,23 +12,12 @@ export function entropySignCommand () {
.addOption(accountOption())
.addOption(configOption())
.addOption(endpointOption())
// .addOption(
// new Option(
// '-r, --raw',
// 'Signs the provided message using the Raw Signing method. Output is a signature (string)'
// )
// )
.action(async (msg, opts) => {
const entropy = await loadEntropyCli(opts)
const SigningService = new EntropySign(entropy, opts.endpoint)
// TO-DO: Add ability for raw signing here, maybe? new raw option can be used for the conditional
/**
* if (opts.raw) {
* implement raw sign here
* }
*/
const { verifyingKey, signature } = await SigningService.signMessageWithAdapters({ msg })
cliWrite({ verifyingKey, signature })

const signatureData = await SigningService.signMessageWithAdapters({ msg })
cliWrite(stringify(signatureData, 0))
process.exit(0)
})
return signCommand
Expand Down
32 changes: 4 additions & 28 deletions src/sign/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,19 @@ import { EntropyTuiOptions } from '../types'

export async function entropySign (entropy: Entropy, opts: EntropyTuiOptions) {
const signingService = new EntropySign(entropy, opts.endpoint)
// const { interactionChoice } = await inquirer.prompt(interactionChoiceQuestions)
// switch (interactionChoice) {
// case 'Raw Sign': {
// const { msg, msgPath } = await getMsgFromUser(inquirer)
// const { hashingAlgorithm, auxiliaryDataFile } = await inquirer.prompt(rawSignParamsQuestions)
// let hash = hashingAlgorithm
// const auxiliaryData = JSON.parse(readFileSync(auxiliaryDataFile).toString())
// if (JSON.parse(hashingAlgorithm)) {
// hash = JSON.parse(hashingAlgorithm)
// }

// const { signature, verifyingKey } = await Sign.rawSignMessage({ msg, msgPath, hashingAlgorithm: hash, auxiliaryData })
// print('msg to be signed:', msg)
// print('verifying key:', verifyingKey)
// print('signature:', signature)
// return
// }
// case 'Sign With Adapter': {
try {
const { msg } = await getMsgFromUser(inquirer)
const { signature, verifyingKey } = await signingService.signMessageWithAdapters({ msg })
print('msg to be signed:', msg)
const { message, signature, verifyingKey, hashType } = await signingService.signMessageWithAdapters({ msg })
print('msg signed:', message)
print('verifying key:', verifyingKey)
print('hashing algorithm:', hashType)
print('signature:', signature)
return
} catch (error) {
if (!entropy.signingManager.verifyingKey) {
console.error('Please register your Entropy account before signing');
print.error('Please register your Entropy account before signing')
return 'exit'
}
throw error
}
// return
// }
// case 'Exit to Main Menu':
// return 'exit'
// default:
// throw new Error('Unrecognizable action')
// }
}
43 changes: 5 additions & 38 deletions src/sign/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Entropy from "@entropyxyz/sdk"
import { u8aToHex } from '@polkadot/util'
import { EntropyBase } from "../common/entropy-base";
import { SignResult } from "./types";
import { SignatureData } from "./types";
import { FLOW_CONTEXT } from "./constants";
import { stringToHex } from "./utils";

Expand All @@ -10,39 +9,11 @@ export class EntropySign extends EntropyBase {
super({ entropy, endpoint, flowContext: FLOW_CONTEXT })
}

// async rawSign (entropy: Entropy, payload: RawSignPayload) {
// return entropy.sign(payload)
// }

// async rawSignMessage ({ msg, msgPath, auxiliaryData, hashingAlgorithm }): Promise<SignResult> {
// const message = getMsgFromInputOrFile(msg, msgPath)

// try {
// this.logger.log(`Msg to be signed: ${msg}`, 'SIGN_MSG')
// this.logger.log( `Verifying Key used: ${this.entropy.signingManager.verifyingKey}`)
// const signature = await rawSign(
// this.entropy,
// {
// sigRequestHash: stringAsHex(message),
// hash: hashingAlgorithm,
// auxiliaryData
// }
// )
// const signatureHexString = u8aToHex(signature)
// this.logger.log(`Signature: ${signatureHexString}`)

// return { signature: signatureHexString, verifyingKey: this.entropy.signingManager.verifyingKey }
// } catch (error) {
// this.logger.error('Error signing message', error)
// throw error
// }
// }

async signMessageWithAdapters ({ msg }: { msg: string }): Promise<SignResult> {
async signMessageWithAdapters ({ msg }: { msg: string }): Promise<SignatureData> {
try {
this.logger.log(`Msg to be signed: ${msg}`, 'SIGN_MSG')
this.logger.log( `Verifying Key used: ${this.entropy.signingManager.verifyingKey}`)
const signature: any = await this.entropy.signWithAdaptersInOrder({
const signatureData: any = await this.entropy.signWithAdaptersInOrder({
msg: {
msg: stringToHex(msg)
},
Expand All @@ -52,13 +23,9 @@ export class EntropySign extends EntropyBase {
// auxillaryData
})

const signatureHexString = u8aToHex(signature)
this.logger.log(`Signature: ${signatureHexString}`)
this.logger.log(`Signature: ${signatureData.signature}`)

return {
signature: signatureHexString,
verifyingKey: this.entropy.signingManager.verifyingKey
}
return signatureData
} catch (error) {
this.logger.error('Error signing message', error)
throw error
Expand Down
13 changes: 8 additions & 5 deletions src/sign/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export interface SignResult {
signature: string
verifyingKey: string
}

export interface RawSignPayload {
sigRequestHash: string
hash: any
auxiliaryData: any
signatureVerifyingKey?: string
}

// TODO: export type from sdk
export interface SignatureData {
signature: string
verifyingKey: string
hashType: string
message: string // hex string as bytes?
}
22 changes: 1 addition & 21 deletions src/sign/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,31 +51,11 @@ export const rawSignParamsQuestions = [
]

export async function getMsgFromUser (inquirer) {
// let msg: string
// let msgPath: string
// const { messageAction } = await inquirer.prompt(messageActionQuestions)
// switch (messageAction) {
// case 'Text Input': {
const { userInput } = await inquirer.prompt(userInputQuestions)
const msg = userInput
// break
// }
// Msg input from a file requires more design
// case 'From a File': {
// const { pathToFile } = await inquirer.prompt(filePathInputQuestions)
// // TODO: relative/absolute path? encoding?
// msgPath = pathToFile
// break
// }
// default: {
// const error = new Error('SigningError: Unsupported User Input Action')
// this.logger.error('Error signing with adapter', error)
// return
// }
// }

return {
msg,
// msgPath
};
}

Expand Down
23 changes: 23 additions & 0 deletions src/verify/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Command, /* Option */ } from 'commander'

import { EntropyVerify } from './main'
import { accountOption, configOption, endpointOption, cliWrite } from '../common/utils-cli'
import { loadEntropyCli } from '../common/load-entropy'

export function entropyVerifyCommand () {
const verifyCommand = new Command('verify')
.description('Verify a signture. Output is a boolean')
.argument('<signatureData>', 'Signature data returned from signing method')
.addOption(accountOption())
.addOption(configOption())
.addOption(endpointOption())
.action(async (signatureData, opts) => {
const entropy = await loadEntropyCli(opts)
const VerifyService = new EntropyVerify(entropy, opts.endpoint)

const isVerified = await VerifyService.verify(JSON.parse(signatureData))
cliWrite({ isSignatureVerified: isVerified })
process.exit(0)
})
return verifyCommand
}
15 changes: 15 additions & 0 deletions src/verify/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Entropy from "@entropyxyz/sdk";
import { EntropyBase } from "src/common/entropy-base";
import { SignatureData } from "src/sign/types";

const FLOW_CONTEXT = 'ENTROPY_VERIFY'

export class EntropyVerify extends EntropyBase {
constructor (entropy: Entropy, endpoint: string) {
super({ entropy, endpoint, flowContext: FLOW_CONTEXT })
}

async verify (signatureData: SignatureData): Promise<boolean> {
return this.entropy.verify(signatureData)
}
}
30 changes: 30 additions & 0 deletions tests/verify.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import test from 'tape'

import { EntropySign } from '../src/sign/main'
import { EntropyVerify } from '../src/verify/main'
import { setupTest, eveSeed } from './testing-utils'

const endpoint = 'ws://127.0.0.1:9944'

test('Verify Signature', async (t) => {
const { run, entropy } = await setupTest(t, { seed: eveSeed })
const signService = new EntropySign(entropy, endpoint)
const verifyService = new EntropyVerify(entropy, endpoint)

await run('register', entropy.register())
const result = await run(
'sign',
signService.signMessageWithAdapters({ msg: "heyo!" })
)

t.true(result?.signature?.length > 32, 'signature has some body!')

const isVerified = await run(
'verify signature',
verifyService.verify(result)
)

t.true(isVerified, 'signature is verified')

t.end()
})
Loading

0 comments on commit ced0f47

Please sign in to comment.