Skip to content

Commit

Permalink
Jawn/sandbox v2 (#252)
Browse files Browse the repository at this point in the history
* tests passing with latest version of core

* register fix and test

* index and keys

* unit tests and key gens

* programs test passes

* register tests pass

* register passes

* clean

* update chacha

* changes from review

* do checks on account keys (#254)

* do checks on account keys

* keys adjustments. tests passing

* lint no longer displaying error

* formatting

* uncommented programModKeyPair validPair check

* updated documentation

* typo

---------

Co-authored-by: jawndiego <[email protected]>

* Also run GitHub Actions workflows (tests) when PRs are updated.

* yaml

* removed keys from entropy class as it was replaced by account

* format

* removed console logs

* added test specific consoling

---------

Co-authored-by: Frankie <[email protected]>
Co-authored-by: Violet Parr <[email protected]>
  • Loading branch information
3 people authored Nov 29, 2023
1 parent 95eff6e commit c7550a6
Show file tree
Hide file tree
Showing 33 changed files with 8,833 additions and 19,576 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ on:
pull_request:
types:
- opened
- synchronize
- reopened

jobs:
build_test_lint:
Expand Down
6 changes: 1 addition & 5 deletions entropy-metadata.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"devDependencies": {
"@babel/preset-typescript": "^7.21.0",
"@changesets/cli": "^2.26.0",
"@polkadot/typegen": "^10.9.1",
"@polkadot/typegen": "^10.11.1",
"@swc/core": "^1.3.32",
"@tsconfig/node18": "^1.0.1",
"@types/jest": "^29.4.0",
Expand All @@ -75,11 +75,11 @@
"typescript": "^4.9.5"
},
"dependencies": {
"@entropyxyz/x25519-chacha20poly1305-nodejs": "^0.1.1",
"@entropyxyz/x25519-chacha20poly1305-web": "^0.1.3",
"@entropyxyz/x25519-chacha20poly1305-nodejs": "^0.2.0",
"@entropyxyz/x25519-chacha20poly1305-web": "^0.2.0",
"@ethersproject/bytes": "^5.7.0",
"@polkadot/api": "^10.9.1",
"@polkadot/typegen": "^10.9.1",
"@polkadot/api": "^10.11.1",
"@polkadot/typegen": "^10.11.1",
"ethers": "^5.7.2"
},
"directories": {
Expand Down
198 changes: 128 additions & 70 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { ApiPromise, WsProvider } from '@polkadot/api'
import { isValidSubstrateAddress } from './utils'
import RegistrationManager, { RegistrationParams } from './registration'
import { getWallet } from './keys'
import SignatureRequestManager, { SigOps, SigTxOps } from './signing'
import { crypto } from './utils/crypto'
import { Adapter } from './signing/adapters/types'
import { isValidPair } from './keys'
import { Signer, Address } from './types'
import ProgramManager from './programs'

export interface EntropyAccount {
sigRequestKey?: Signer
programModKey?: Signer | string
}

export interface EntropyOpts {
/** seed for wallet initialization. */
seed?: string
/** account for wallet initialization. */
account?: EntropyAccount
/** local or devnet endpoint for establishing a connection to validators */
endpoint?: string
/** A collection of signing adapters. */
adapters?: { [key: string | number]: Adapter }
}


/**
*
* @remarks
Expand All @@ -26,142 +30,196 @@ export interface EntropyOpts {
* and sign transactions.
* Users can await the `ready` promise to ensure that the class has been initialized
* before performing operations.
*
*
* @example
* ```typescript
* const entropy = new Entropy({ seed: 'SEED', endpoint: 'wss://localhost:8080' })
* await entropy.ready
* await entropy.register({ address, keyVisibility: 'Permissioned', freeTx: false })
*
const signer = await getWallet(charlieStashSeed)
const entropyAccount: EntropyAccount = {
sigRequestKey: signer.pair,
programModKey: signer.pair
}
const entropy = new Entropy({ account: entropyAccount})
await entropy.ready
await entropy.register({ address, keyVisibility: 'Permissioned', freeTx: false })
* ```
* @alpha
*/

export default class Entropy {
#ready?: (value?: unknown) => void
#fail?: (reason?: unknown) => void

#programReadOnly: boolean
#allReadOnly: boolean
/** A promise that resolves once chacha20poly1305 cryptoLib has been loaded */
ready: Promise<void>
keys?: Signer
public sigRequestPublicKey?: string
public programModPublicKey?: string
registrationManager: RegistrationManager
isRegistered: (address: Address) => Promise<boolean>
programs: ProgramManager
signingManager: SignatureRequestManager

account?: EntropyAccount
substrate: ApiPromise

async init (opts: EntropyOpts) {
this.keys = await getWallet(opts.seed)
constructor (opts: EntropyOpts) {
this.ready = new Promise((resolve, reject) => {
this.#ready = resolve
this.#fail = reject
})

this.#init(opts).catch((error) => {
this.#fail(error)
})
}

async #init (opts: EntropyOpts) {
this.account = opts.account
this.#setReadOnlyStates()

const wsProvider = new WsProvider(opts.endpoint)
this.substrate = new ApiPromise({ provider: wsProvider })
await this.substrate.isReady

const substrate = new ApiPromise({ provider: wsProvider })
this.substrate = substrate
this.registrationManager = new RegistrationManager({
substrate: substrate,
signer: this.keys,
substrate: this.substrate,
signer: {wallet: this.account.sigRequestKey.wallet, pair: this.account.sigRequestKey.pair},
})
this.signingManager = new SignatureRequestManager({
signer: this.keys,
substrate,
signer: {wallet: this.account.sigRequestKey.wallet, pair: this.account.sigRequestKey.pair},
substrate: this.substrate,
adapters: opts.adapters,
crypto,
})

const programModKeyPair = isValidPair(this.account.programModKey as Signer) ? this.account.programModKey : undefined

this.programs = new ProgramManager({
substrate: substrate,
signer: this.keys,
substrate: this.substrate,
signer: programModKeyPair as Signer || this.account.sigRequestKey,
})
await substrate.isReady
if (this.#programReadOnly || this.#allReadOnly) this.programs.set = async () => { throw new Error('Programs is in a read only state: Must pass a valid key pair in initialization.') }
this.#ready()
this.isRegistered = this.registrationManager.checkRegistrationStatus.bind(
this.registrationManager
)
}

constructor (opts: EntropyOpts) {
this.ready = new Promise((resolve, reject) => {
this.#ready = resolve
this.#fail = reject
})
#setReadOnlyStates (): void {
// the readOnly state will not allow for write functions
this.#programReadOnly = false
this.#allReadOnly = false

this.init(opts).catch((error) => {
this.#fail(error)
})
if (!this.account) {
this.#allReadOnly = true
} else if (!this.account.sigRequestKey && !this.account.programModKey) {
this.#allReadOnly = true
}


if (typeof this.account.sigRequestKey !== 'object') {
throw new Error('AccountTypeError: sigRequestKey can not be a string')
} else if (!isValidPair({ wallet: this.account.sigRequestKey.wallet, pair: this.account.sigRequestKey.pair})) {
throw new Error('AccountTypeError: sigRequestKey not a valid signing pair')
}

if (typeof this.account.programModKey === 'string') {
if (!isValidSubstrateAddress(this.account.programModKey)) {
throw new Error('AccountTypeError: programModKey not a valid address')
}
this.#programReadOnly = true
}
}


/**
* Registers an address to Entropy using the provided parameters.
*
* @param registrationParams - Parameters for registration, including:
* - `address`: The address to register
* - `programModAccount`: The address of the account authorized to set program's on the sigRequestKey's behalf
* - `keyVisibility`: The visibility setting for the key. "Private" | "Public" | "Permissioned"
* - `initialProgram`: (optional for now) Initial program setting. TODO // update to reflect new settings
* @returns A promise indicating the completion of the registration process.
* @throws {TypeError} Throws if the provided address format is not compatible.
* @throws {Error} Throws if the address being registered is already in use.
*/

async register (params: RegistrationParams): Promise<undefined> {
async register (
params: RegistrationParams & { account?: EntropyAccount }
): Promise<void> {
await this.ready
if (params.address) {
if (!isValidSubstrateAddress(params.address)) {
throw new TypeError('Incompatible address type')
}
if (this.#allReadOnly) throw new Error('Initialized in read only state: can not use write functions')
const account = params.account || this.account

if (!account) {
throw new Error('No account provided for registration')
}

if (
params.programModAccount &&
!isValidSubstrateAddress(params.programModAccount)
) {
throw new TypeError('Incompatible address type')
}

return this.registrationManager.register(params)
}

/**
* Signs a given transaction based on the provided parameters.
*
* The `signTransaction` method invokes the appropriate adapter (chain based configuration)
* based on the type specified in the `params`. This modular approach ensures that various
* transaction types can be supported. The method performs a series of operations, starting
* with the `preSign` function of the selected adapter, followed by the actual signing of the
*
* The `signTransaction` method invokes the appropriate adapter (chain based configuration)
* based on the type specified in the `params`. This modular approach ensures that various
* transaction types can be supported. The method performs a series of operations, starting
* with the `preSign` function of the selected adapter, followed by the actual signing of the
* transaction request hash, and if necessary, the `postSign` function of the adapter.
*
* @param params - An object that encapsulates all the required parameters for signing.
* @param params.txParams - Transaction parameters specific to the adapter being used.
* @param params.type - (Optional) A string indicating the type of the transaction which
* @param params.type - (Optional) A string indicating the type of the transaction which
* helps select the appropriate adapter for signing.
*
* @returns A promise that returns the transaction signature. Note that the structure
* and format of this signature may differ based on the adapter.
* @returns A promise that returns the transaction signature. Note that the structure
* and format of this signature may differ based on the adapter.
* @throws {Error} Will throw an error if the transaction type does not have a corresponding adapter.
*/

async signTransaction (params: SigTxOps): Promise<unknown> {
await this.ready
if (this.#allReadOnly) throw new Error('Initialized in read only state: can not use write functions')
return this.signingManager.signTransaction(params)
}


/**
* The `sign` method is tasked with signing a `sigRequestHash`, which is essentially a hash of the
* request that needs signing. It does so by obtaining validator information based on the hash,
* formatting transaction requests for these validators, and then submitting these requests for the
* validators to sign.
*
* The process in detail:
* 1. The method removes any hex prefix from the request hash.
* 2. Determines a set of validators corresponding to the stripped request hash. These validators
* are tasked with validating and signing the transaction.
* 3. For each of these validators, the method constructs a transaction request. This request encompasses:
* - The stripped transaction request hash.
* - Information regarding all the chosen validators.
* - A timestamp.
* 4. Transaction requests are individually encrypted and signed for each validator using their respective public keys.
* 5. These encrypted and signed transaction requests are dispatched to the individual validators.
* 6. The method then awaits the validators' signatures on the requests.
* 7. Once received, the signature from the first validator is extracted and returned.
*
* @param params An object `sigRequestHash`, representing the hash of the request awaiting signature.
* @returns A promise which, when resolved, produces a Uint8Array with the signature of the first validator.
* @throws {Error} Throws an error if there's an error at any stage in the signing routine.
*/

/**
* The `sign` method is tasked with signing a `sigRequestHash`, which is essentially a hash of the
* request that needs signing. It does so by obtaining validator information based on the hash,
* formatting transaction requests for these validators, and then submitting these requests for the
* validators to sign.
*
* The process in detail:
* 1. The method removes any hex prefix from the request hash.
* 2. Determines a set of validators corresponding to the stripped request hash. These validators
* are tasked with validating and signing the transaction.
* 3. For each of these validators, the method constructs a transaction request. This request encompasses:
* - The stripped transaction request hash.
* - Information regarding all the chosen validators.
* - A timestamp.
* 4. Transaction requests are individually encrypted and signed for each validator using their respective public keys.
* 5. These encrypted and signed transaction requests are dispatched to the individual validators.
* 6. The method then awaits the validators' signatures on the requests.
* 7. Once received, the signature from the first validator is extracted and returned.
*
* @param params An object `sigRequestHash`, representing the hash of the request awaiting signature.
* @returns A promise which, when resolved, produces a Uint8Array with the signature of the first validator.
* @throws {Error} Throws an error if there's an error at any stage in the signing routine.
*/

async sign (params: SigOps): Promise<Uint8Array> {
await this.ready
if (this.#allReadOnly) throw new Error('Initialized in read only state: can not use write functions')
return this.signingManager.sign(params)
}
}
Loading

0 comments on commit c7550a6

Please sign in to comment.