Skip to content

Commit

Permalink
Merge pull request #1867 from aeternity/feature/delegation-over-aepp-…
Browse files Browse the repository at this point in the history
…wallet

Delegation signature over aepp-wallet connection
  • Loading branch information
davidyuk committed Jul 27, 2023
2 parents 17ff38e + 30fad96 commit 49e57e7
Show file tree
Hide file tree
Showing 16 changed files with 497 additions and 19 deletions.
10 changes: 9 additions & 1 deletion examples/browser/aepp/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
>
Typed data
</a>
<a
href="#"
:class="{ active: view === 'DelegationSignature' }"
@click="view = 'DelegationSignature'"
>
Delegation signature
</a>
</div>

<Component
Expand All @@ -47,10 +54,11 @@ import Basic from './Basic.vue';
import Contracts from './Contracts.vue';
import PayForTx from './PayForTx.vue';
import TypedData from './TypedData.vue';
import DelegationSignature from './DelegationSignature.vue';
export default {
components: {
Connect, Basic, Contracts, PayForTx, TypedData,
Connect, Basic, Contracts, PayForTx, TypedData, DelegationSignature,
},
data: () => ({ view: '' }),
computed: mapState(['aeSdk']),
Expand Down
68 changes: 68 additions & 0 deletions examples/browser/aepp/src/DelegationSignature.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<h2>Sign delegation to contract</h2>
<div class="group">
<div>
<div>Contract address</div>
<div><input v-model="contractAddress"></div>
</div>
<div>
<label>
<input v-model="type" type="radio" value="general">
AENS and oracle
</label>
</div>
<div>
<label>
<input v-model="type" type="radio" value="name">
AENS name
</label>
<div><input v-model="name"></div>
</div>
<div>
<label>
<input v-model="type" type="radio" value="oracle-query">
Oracle query
</label>
<div><input v-model="oracleQueryId"></div>
</div>
<button @click="signPromise = sign()">
Sign
</button>
<div v-if="signPromise">
<div>Signature</div>
<Value :value="signPromise" />
</div>
</div>
</template>

<script>
import { mapState } from 'vuex';
import Value from './components/Value.vue';
export default {
components: { Value },
data: () => ({
type: 'general',
contractAddress: 'ct_6y3N9KqQb74QsvR9NrESyhWeLNiA9aJgJ7ua8CvsTuGot6uzh',
name: 'test.chain',
oracleQueryId: 'oq_6y3N9KqQb74QsvR9NrESyhWeLNiA9aJgJ7ua8CvsTuGot6uzh',
signPromise: null,
}),
computed: mapState(['aeSdk']),
methods: {
sign() {
switch (this.type) {
case 'general':
return this.aeSdk.signDelegationToContract(this.contractAddress);
case 'name':
return this.aeSdk.signNameDelegationToContract(this.contractAddress, this.name);
case 'oracle-query':
return this.aeSdk
.signOracleQueryDelegationToContract(this.contractAddress, this.oracleQueryId);
default:
throw new Error(`Unknown delegation signature type: ${this.type}`)
}
},
},
};
</script>
35 changes: 35 additions & 0 deletions examples/browser/wallet-iframe/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,41 @@ export default {
return super.signTypedData(data, aci, options);
}
async signDelegationToContract(
contractAddress,
{ aeppRpcClientId: id, aeppOrigin, ...options },
) {
if (id != null) {
const opt = { ...options, contractAddress };
genConfirmCallback('sign delegation to contract')(id, opt, aeppOrigin);
}
return super.signDelegationToContract(contractAddress, options);
}
async signNameDelegationToContract(
contractAddress,
name,
{ aeppRpcClientId: id, aeppOrigin, ...options },
) {
if (id != null) {
const opt = { ...options, contractAddress, name };
genConfirmCallback('sign delegation of name to contract')(id, opt, aeppOrigin);
}
return super.signNameDelegationToContract(contractAddress, name, options);
}
async signOracleQueryDelegationToContract(
contractAddress,
oracleQueryId,
{ aeppRpcClientId: id, aeppOrigin, ...options },
) {
if (id != null) {
const opt = { ...options, contractAddress, oracleQueryId };
genConfirmCallback('sign delegation of oracle query to contract')(id, opt, aeppOrigin);
}
return super.signOracleQueryDelegationToContract(contractAddress, oracleQueryId, options);
}
static generate() {
// TODO: can inherit parent method after implementing https://github.com/aeternity/aepp-sdk-js/issues/1672
return new AccountMemoryProtected(generateKeyPair().secretKey);
Expand Down
35 changes: 35 additions & 0 deletions examples/browser/wallet-web-extension/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,41 @@ class AccountMemoryProtected extends MemoryAccount {
return super.signTypedData(data, aci, options);
}

async signDelegationToContract(
contractAddress,
{ aeppRpcClientId: id, aeppOrigin, ...options },
) {
if (id != null) {
const opt = { ...options, contractAddress };
await genConfirmCallback('sign delegation to contract')(id, opt, aeppOrigin);
}
return super.signDelegationToContract(contractAddress, options);
}

async signNameDelegationToContract(
contractAddress,
name,
{ aeppRpcClientId: id, aeppOrigin, ...options },
) {
if (id != null) {
const opt = { ...options, contractAddress, name };
await genConfirmCallback('sign delegation of name to contract')(id, opt, aeppOrigin);
}
return super.signNameDelegationToContract(contractAddress, name, options);
}

async signOracleQueryDelegationToContract(
contractAddress,
oracleQueryId,
{ aeppRpcClientId: id, aeppOrigin, ...options },
) {
if (id != null) {
const opt = { ...options, contractAddress, oracleQueryId };
await genConfirmCallback('sign delegation of oracle query to contract')(id, opt, aeppOrigin);
}
return super.signOracleQueryDelegationToContract(contractAddress, oracleQueryId, options);
}

static generate() {
// TODO: can inherit parent method after implementing https://github.com/aeternity/aepp-sdk-js/issues/1672
return new AccountMemoryProtected(generateKeyPair().secretKey);
Expand Down
36 changes: 36 additions & 0 deletions src/AeSdkBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import { Encoded } from './utils/encoder';
import CompilerBase from './contract/compiler/Base';
import AeSdkMethods, { OnAccount, getValueOrErrorProxy, AeSdkMethodsOptions } from './AeSdkMethods';
import { AensName } from './tx/builder/constants';

type NodeInfo = Awaited<ReturnType<Node['getNodeInfo']>> & { name: string };

Expand Down Expand Up @@ -176,6 +177,41 @@ export default class AeSdkBase extends AeSdkMethods {
return this._resolveAccount(onAccount).signTypedData(data, aci, options);
}

async signDelegationToContract(
contractAddress: Encoded.ContractAddress,
{ onAccount, ...options }: { onAccount?: OnAccount }
& Parameters<AccountBase['signDelegationToContract']>[1] = {},
): Promise<Encoded.Signature> {
options.networkId ??= this.selectedNodeName !== null
? await this.api.getNetworkId() : undefined;
return this._resolveAccount(onAccount)
.signDelegationToContract(contractAddress, options);
}

async signNameDelegationToContract(
contractAddress: Encoded.ContractAddress,
name: AensName,
{ onAccount, ...options }: { onAccount?: OnAccount }
& Parameters<AccountBase['signNameDelegationToContract']>[2] = {},
): Promise<Encoded.Signature> {
options.networkId ??= this.selectedNodeName !== null
? await this.api.getNetworkId() : undefined;
return this._resolveAccount(onAccount)
.signNameDelegationToContract(contractAddress, name, options);
}

async signOracleQueryDelegationToContract(
contractAddress: Encoded.ContractAddress,
oracleQueryId: Encoded.OracleQueryId,
{ onAccount, ...options }: { onAccount?: OnAccount }
& Parameters<AccountBase['signOracleQueryDelegationToContract']>[2] = {},
): Promise<Encoded.Signature> {
options.networkId ??= this.selectedNodeName !== null
? await this.api.getNetworkId() : undefined;
return this._resolveAccount(onAccount)
.signOracleQueryDelegationToContract(contractAddress, oracleQueryId, options);
}

override _getOptions(callOptions: AeSdkMethodsOptions = {}): {
onNode: Node;
onAccount: AccountBase;
Expand Down
18 changes: 18 additions & 0 deletions src/AeSdkWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,24 @@ export default class AeSdkWallet extends AeSdk {
signature: await this.signTypedData(data, aci, parameters),
};
},
[METHODS.signDelegationToContract]: async ({
contractAddress, name, oracleQueryId, onAccount = this.address,
}, origin) => {
if (!this._isRpcClientConnected(id)) throw new RpcNotAuthorizeError();
if (!this.addresses().includes(onAccount)) {
throw new RpcPermissionDenyError(onAccount);
}

const parameters = { onAccount, aeppOrigin: origin, aeppRpcClientId: id };
const signature = await (
(name == null ? null : this
.signNameDelegationToContract(contractAddress, name, parameters))
?? (oracleQueryId == null ? null : this
.signOracleQueryDelegationToContract(contractAddress, oracleQueryId, parameters))
?? this.signDelegationToContract(contractAddress, parameters)
);
return { signature };
},
},
),
};
Expand Down
87 changes: 84 additions & 3 deletions src/account/Base.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Encoded } from '../utils/encoder';
import Node from '../Node';
import CompilerBase from '../contract/compiler/Base';
import { Int } from '../tx/builder/constants';
import { AensName, Int } from '../tx/builder/constants';
import { AciValue, Domain } from '../utils/typed-data';
import { NotImplementedError } from '../utils/errors';

interface AuthData {
fee?: Int;
Expand Down Expand Up @@ -60,14 +61,94 @@ export default abstract class AccountBase {
* @param options - Options
* @returns Signature
*/
abstract signTypedData(
// TODO: make abstract in the next major release
// eslint-disable-next-line class-methods-use-this
async signTypedData(
/* eslint-disable @typescript-eslint/no-unused-vars */
data: Encoded.ContractBytearray,
aci: AciValue,
options?: Domain & {
aeppOrigin?: string;
aeppRpcClientId?: string;
},
): Promise<Encoded.Signature>;
/* eslint-enable @typescript-eslint/no-unused-vars */
): Promise<Encoded.Signature> {
throw new NotImplementedError('signTypedData method');
}

/**
* Sign delegation of AENS, oracle operations to a contract
* @param contractAddress - Address of a contract to delegate permissions to
* @param options - Options
* @returns Signature
*/
// TODO: make abstract in the next major release
// eslint-disable-next-line class-methods-use-this
async signDelegationToContract(
/* eslint-disable @typescript-eslint/no-unused-vars */
contractAddress: Encoded.ContractAddress,
options?: {
networkId?: string;
aeppOrigin?: string;
aeppRpcClientId?: string;
},
/* eslint-enable @typescript-eslint/no-unused-vars */
): Promise<Encoded.Signature> {
throw new NotImplementedError('signDelegationToContract method');
}

/**
* Sign delegation of an AENS name to a contract
* @param contractAddress - Address of a contract to delegate permissions to
* @param name - AENS name to manage by a contract
* @param options - Options
* @returns Signature
*/
// TODO: make abstract in the next major release
// eslint-disable-next-line class-methods-use-this
async signNameDelegationToContract(
/* eslint-disable @typescript-eslint/no-unused-vars */
contractAddress: Encoded.ContractAddress,
name: AensName,
options?: {
networkId?: string;
aeppOrigin?: string;
aeppRpcClientId?: string;
},
/* eslint-enable @typescript-eslint/no-unused-vars */
): Promise<Encoded.Signature> {
throw new NotImplementedError('signNameDelegationToContract method');
}

/**
* Sign delegation of oracle query to a contract
*
* Warning! Implementations needs to ensure that decoded oracle query id is not equal to decoded
* current account address unless https://github.com/aeternity/aesophia/issues/475 is fixed.
*
* Warning! Implementations needs to ensure that oracle query and contract exists unless
* https://github.com/aeternity/aesophia/issues/474 is fixed.
*
* @param contractAddress - Address of a contract to delegate permissions to
* @param oracleQueryId - Oracle query ID to reply by a contract
* @param options - Options
* @returns Signature
*/
// TODO: make abstract in the next major release
// eslint-disable-next-line class-methods-use-this
async signOracleQueryDelegationToContract(
/* eslint-disable @typescript-eslint/no-unused-vars */
contractAddress: Encoded.ContractAddress,
oracleQueryId: Encoded.OracleQueryId,
options?: {
networkId?: string;
aeppOrigin?: string;
aeppRpcClientId?: string;
},
/* eslint-enable @typescript-eslint/no-unused-vars */
): Promise<Encoded.Signature> {
throw new NotImplementedError('signOracleQueryDelegationToContract method');
}

/**
* Sign data blob
Expand Down
15 changes: 15 additions & 0 deletions src/account/Generalized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ export default class AccountGeneralized extends AccountBase {
throw new NotImplementedError('Can\'t sign using generalized account');
}

// eslint-disable-next-line class-methods-use-this
override async signDelegationToContract(): Promise<Encoded.Signature> {
throw new NotImplementedError('signing delegation to contract using generalized account');
}

// eslint-disable-next-line class-methods-use-this
override async signNameDelegationToContract(): Promise<Encoded.Signature> {
throw new NotImplementedError('signing delegation to contract using generalized account');
}

// eslint-disable-next-line class-methods-use-this
override async signOracleQueryDelegationToContract(): Promise<Encoded.Signature> {
throw new NotImplementedError('signing delegation to contract using generalized account');
}

override async signTransaction(
tx: Encoded.Transaction,
{ authData, onCompiler, onNode }: Parameters<AccountBase['signTransaction']>[1],
Expand Down

0 comments on commit 49e57e7

Please sign in to comment.