Skip to content

Commit

Permalink
feat(aepp,wallet): allow raw data sign
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Mar 28, 2024
1 parent 9c08860 commit 7d0136d
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 9 deletions.
7 changes: 6 additions & 1 deletion examples/browser/aepp/src/Basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,21 @@
<SpendCoins />

<MessageSign />

<DataSign />
</template>

<script>
import { mapState } from 'vuex';
import Value from './components/Value.vue';
import SpendCoins from './components/SpendCoins.vue';
import MessageSign from './components/MessageSign.vue';
import DataSign from './components/DataSign.vue';
export default {
components: { Value, SpendCoins, MessageSign },
components: {
Value, SpendCoins, MessageSign, DataSign,
},
data: () => ({
balancePromise: null,
heightPromise: null,
Expand Down
76 changes: 76 additions & 0 deletions examples/browser/aepp/src/components/DataSign.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<template>
<h2>Sign raw data (unsafe)</h2>
<div class="group">
<div>
<div>Data as text</div>
<div>
<input
:value="dataBuffer.toString()"
@input="setData($event.target.value)"
placeholder="Plain text"
>
</div>
</div>
<div>
<div>Data as hex</div>
<div>
<input
:value="dataBuffer.toString('hex')"
@input="setData($event.target.value, 'hex')"
placeholder="hex-encoded data"
>
</div>
</div>
<div>
<div>Data encoded</div>
<div>
<input
v-model="data"
placeholder="ba_-encoded data"
>
</div>
</div>
<button @click="() => { promise = dataSign(); }">
Sign data
</button>
<div v-if="promise">
<div>Data sign result</div>
<Value :value="promise" />
</div>
</div>
</template>

<script>
import { mapState } from 'vuex';
import { Buffer } from 'buffer';
import { decode, encode, Encoding } from '@aeternity/aepp-sdk';
import Value from './Value.vue';
const emptyData = encode(Buffer.from([]), Encoding.Bytearray);
export default {
components: { Value },
computed: {
...mapState(['aeSdk']),
dataBuffer() {
try {
return Buffer.from(decode(this.data || emptyData));
} catch (error) {
return Buffer.from([]);
}
},
},
data: () => ({
data: '',
promise: null,
}),
methods: {
setData(data, type) {
this.data = encode(Buffer.from(data, type), Encoding.Bytearray);
},
dataSign() {
return this.aeSdk.sign(decode(this.data || emptyData));
},
},
};
</script>
7 changes: 7 additions & 0 deletions examples/browser/wallet-iframe/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ export default {
return super.signOracleQueryDelegationToContract(contractAddress, oracleQueryId, options);
}

async sign(data, { aeppRpcClientId: id, aeppOrigin, ...options } = {}) {
if (id != null) {
genConfirmCallback(`sign raw data ${data}`)(id, options, aeppOrigin);
}
return super.sign(data, options);
}

async signDelegation(delegation, { aeppRpcClientId: id, aeppOrigin, ...options }) {
if (id != null) {
const opt = { ...options, ...unpackDelegation(delegation) };
Expand Down
9 changes: 8 additions & 1 deletion examples/browser/wallet-web-extension/src/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class AccountMemoryProtected extends MemoryAccount {
) {
if (id != null) {
const opt = { ...options, contractAddress };
genConfirmCallback('sign delegation of all names to contract')(id, opt, aeppOrigin);
await genConfirmCallback('sign delegation of all names to contract')(id, opt, aeppOrigin);
}
return super.signAllNamesDelegationToContract(contractAddress, options);
}
Expand All @@ -124,6 +124,13 @@ class AccountMemoryProtected extends MemoryAccount {
return super.signOracleQueryDelegationToContract(contractAddress, oracleQueryId, options);
}

async sign(data, { aeppRpcClientId: id, aeppOrigin, ...options } = {}) {
if (id != null) {
await genConfirmCallback(`sign raw data ${data}`)(id, options, aeppOrigin);
}
return super.sign(data, options);
}

async signDelegation(delegation, { aeppRpcClientId: id, aeppOrigin, ...options }) {
if (id != null) {
const opt = { ...options, ...unpackDelegation(delegation) };
Expand Down
11 changes: 10 additions & 1 deletion src/AeSdkWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import {
WalletApi,
WalletInfo,
} from './aepp-wallet-communication/rpc/types';
import { Encoded } from './utils/encoder';
import {
Encoded, Encoding, encode, decode,
} from './utils/encoder';
import jsonBig from './utils/json-big';

type RpcClientWallet = RpcClient<AeppApi, WalletApi>;
Expand Down Expand Up @@ -322,6 +324,13 @@ export default class AeSdkWallet extends AeSdk {
);
return { signature };
},
[METHODS.unsafeSign]: async ({ data, 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 = encode(await this.sign(decode(data), parameters), Encoding.Signature);
return { signature };
},
[METHODS.signDelegation]: async ({ delegation, onAccount = this.address }, origin) => {
if (!this._isRpcClientConnected(id)) throw new RpcNotAuthorizeError();
if (!this.addresses().includes(onAccount)) throw new RpcPermissionDenyError(onAccount);
Expand Down
8 changes: 7 additions & 1 deletion src/account/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,13 @@ export default abstract class AccountBase {
* @param options - Options
* @returns Signature
*/
abstract sign(data: string | Uint8Array, options?: any): Promise<Uint8Array>;
abstract sign(
data: string | Uint8Array,
options?: {
aeppOrigin?: string;
aeppRpcClientId?: string;
},
): Promise<Uint8Array>;

/**
* Account address
Expand Down
14 changes: 9 additions & 5 deletions src/account/Rpc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import AccountBase from './Base';
import { METHODS } from '../aepp-wallet-communication/schema';
import { ArgumentError, NotImplementedError, UnsupportedProtocolError } from '../utils/errors';
import { Encoded } from '../utils/encoder';
import { ArgumentError, UnsupportedProtocolError } from '../utils/errors';
import {
Encoded, Encoding, decode, encode,
} from '../utils/encoder';
import RpcClient from '../aepp-wallet-communication/rpc/RpcClient';
import { AeppApi, WalletApi } from '../aepp-wallet-communication/rpc/types';
import { AensName, ConsensusProtocolVersion } from '../tx/builder/constants';
Expand All @@ -26,9 +28,11 @@ export default class AccountRpc extends AccountBase {
this.address = address;
}

// eslint-disable-next-line class-methods-use-this
async sign(): Promise<Uint8Array> {
throw new NotImplementedError('RAW signing using wallet');
async sign(dataRaw: string | Uint8Array): Promise<Uint8Array> {
const data = encode(Buffer.from(dataRaw), Encoding.Bytearray);
const { signature } = await this._rpcClient
.request(METHODS.unsafeSign, { onAccount: this.address, data });
return decode(signature);
}

override async signTransaction(
Expand Down
4 changes: 4 additions & 0 deletions src/aepp-wallet-communication/rpc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export interface WalletApi {

[METHODS.address]: () => Promise<Encoded.AccountAddress[]>;

[METHODS.unsafeSign]: (
p: { data: Encoded.Bytearray; onAccount: Encoded.AccountAddress }
) => Promise<{ signature: Encoded.Signature }>;

[METHODS.sign]: ((
p: {
tx: Encoded.Transaction;
Expand Down
1 change: 1 addition & 0 deletions src/aepp-wallet-communication/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const enum METHODS {
updateAddress = 'address.update',
address = 'address.get',
connect = 'connection.open',
unsafeSign = 'data.unsafeSign',
sign = 'transaction.sign',
signMessage = 'message.sign',
signTypedData = 'typedData.sign',
Expand Down
37 changes: 37 additions & 0 deletions test/integration/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,43 @@ describe('Aepp<->Wallet', function aeppWallet() {
});
});

describe('Sign raw data', () => {
const rawData = Buffer.from('test payload');

it('rejected by wallet', async () => {
let origin;
const s = stub(wallet._resolveAccount(), 'sign').callsFake((data, { aeppOrigin } = {}) => {
origin = aeppOrigin;
throw new RpcRejectedByUserError();
});
await expect(aepp.sign(rawData)).to.be.eventually
.rejectedWith('Operation rejected by user').with.property('code', 4);
expect(origin).to.be.equal('http://origin.test');
s.restore();
});

it('works', async () => {
const signature = await aepp.sign(rawData);
expect(signature).to.be.instanceOf(Buffer);
expect(verify(rawData, signature, aepp.address)).to.be.equal(true);
});

it('fails with unknown error', async () => {
const s = stub(wallet._resolveAccount(), 'sign')
.callsFake(() => { throw new Error('test'); });
await expect(aepp.sign(rawData)).to.be.eventually
.rejectedWith('The peer failed to execute your request due to unknown error')
.with.property('code', 12);
s.restore();
});

it('signs using specific account', async () => {
const onAccount = wallet.addresses()[1];
const signature = await aepp.sign(rawData, { onAccount });
expect(verify(rawData, signature, onAccount)).to.be.equal(true);
});
});

describe('Sign delegation to contract', () => {
const contractAddress = 'ct_6y3N9KqQb74QsvR9NrESyhWeLNiA9aJgJ7ua8CvsTuGot6uzh';

Expand Down

0 comments on commit 7d0136d

Please sign in to comment.