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

Jawn/sandbox v2 #252

Merged
merged 17 commits into from
Nov 29, 2023
Merged
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
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
Loading