Skip to content

Commit

Permalink
refactor: refactor action input validation and encoding
Browse files Browse the repository at this point in the history
action validation & input encoding is now shared between action builder and auth
  • Loading branch information
martin-opensky committed Jan 29, 2025
1 parent b202caf commit 5269c52
Show file tree
Hide file tree
Showing 13 changed files with 371 additions and 267 deletions.
5 changes: 1 addition & 4 deletions src/api_client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,7 @@ export default class Client extends Api {
body: msg.body,
auth_type: msg.auth_type,
sender: msg.sender || '',
signature: {
sig: msg.signature?.sig || '',
type: msg.signature?.type || '',
},
signature: msg.signature || '',
});

const res = await super.post<JsonRPCResponse<CallResponse>>(`/rpc/v1`, body);
Expand Down
77 changes: 49 additions & 28 deletions src/auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import {
removeTrailingSlash,
verifyAuthProperties,
} from '../core/auth';
import { BytesEncodingStatus, EnvironmentType, PayloadType } from '../core/enums';
import { BytesEncodingStatus, EnvironmentType, PayloadType, VarType } from '../core/enums';
import { objects } from '../utils/objects';
import { AuthBody, executeSign, Signature } from '../core/signature';
import { bytesToHex, hexToBytes, stringToBytes } from '../utils/serial';
import { base64ToBytes, bytesToBase64 } from '../utils/base64';
import { ActionBody, ActionInput } from '../core/action';
import { ActionBody, ActionInput, Entries, transformActionInput } from '../core/action';
import { sha256BytesToBytes } from '../utils/crypto';
import { UnencodedActionPayload } from '../core/payload';
import { encodeActionCall } from '../utils/kwilEncoding';
import { encodeActionCall, encodeValue } from '../utils/kwilEncoding';
import { analyzeNumber, formatArguments } from '../utils/parameters';
import { ValidatedAction, NamespaceAction, AccessModifier } from '../transaction/action';
import ActionValidator from '../utils/actionValidator';

interface AuthClient {
getAuthenticateClient(): Promise<GenericResponse<KGWAuthInfo>>;
Expand Down Expand Up @@ -113,35 +116,50 @@ export class Auth<T extends EnvironmentType> {
);
}

// handle if the inputs are an array of ActionInput objects or an array of Entries objects
// TODO: now we are not using the ActionInput objects, we are using the Entries objects
const cleanActionValues = actionBody?.inputs
? actionBody.inputs.map((input) => {
return input instanceof ActionInput ? input.toEntries() : input;
})
: [];
// ActionInput[] is deprecated. So we are converting any ActionInput[] to an Entries[]
let inputs: Entries[] = [];
if (actionBody.inputs && transformActionInput.isActionInputArray(actionBody.inputs)) {
// For a call only one entry is allowed, so we only need to convert the first ActionInput
inputs = transformActionInput.toSingleEntry(actionBody.inputs);
} else if (actionBody.inputs) {
inputs = actionBody.inputs;
}

// retrieve the schema and run validations
const { namespace, actionName, encodedActionInputs, modifiers } =
await ActionValidator.validateActionRequest(actionBody.namespace, actionBody.name, inputs);

// throw a runtime error if more than one set of inputs is trying to be executed. Call does not allow bulk execution.
if (encodedActionInputs && encodedActionInputs.length > 1) {
throw new Error(
'Cannot pass more than one input to the call endpoint. Please pass only one input and try again.'
);
}

const actionValues = actionBody?.inputs ? Object.values(cleanActionValues[0]) : [];
// throw runtime error if action is not a view action. transactions require a different payload structure than view actions.
if (modifiers && modifiers.includes(AccessModifier.VIEW) === false) {
throw new Error(`Action ${actionName} is not a view only action. Please use kwil.execute().`);
}

// create payload
// TODO: Need to test and update
// encodeSingleArguments needs to be review / updated
// construct payload. If there are no prepared actions, then the payload is an empty array.
const payload: UnencodedActionPayload<PayloadType.CALL_ACTION> = {
dbid: actionBody.namespace,
action: actionBody.name,
//arguments: encodeSingleArguments(actionValues),
arguments: [],
dbid: namespace,
action: actionName,
arguments: encodedActionInputs[0], // 'Call' method is used for 'view actions' which require only one input
};

console.log(payload);

// TODO: Need to use encodeActionCall in kwilEncoding.ts
// Need to verify with Luke
//const encodedPayload = kwilEncode(payload);
const encodedPayload = encodeActionCall(payload);
// const encodedPayload = kwilEncode(payload);
// const base64Payload = bytesToBase64(encodedPayload);
const base64Payload = 'test';

// create the digest, which is the first bytes of the sha256 hash of the rlp-encoded payload
const uInt8ArrayPayload = base64ToBytes(base64Payload);
// const uInt8ArrayPayload = base64ToBytes(base64Payload);

const encodedPayload = encodeActionCall(payload);
const uInt8ArrayPayload = base64ToBytes(encodedPayload);

const digest = sha256BytesToBytes(uInt8ArrayPayload).subarray(0, 20);
const msg = generateSignatureText(
actionBody.namespace,
Expand All @@ -150,19 +168,22 @@ export class Auth<T extends EnvironmentType> {
msgChallenge
);

console.log(actionBody.namespace, actionBody.name, bytesToHex(digest), msgChallenge);

const signature = await executeSign(stringToBytes(msg), signer.signer, signer.signatureType);
const sig = bytesToBase64(signature);

const privateSignature: Signature<BytesEncodingStatus.BASE64_ENCODED> = {
sig: sig,
type: signer.signatureType,
};
// const privateSignature: Signature<BytesEncodingStatus.BASE64_ENCODED> = {
// sig: sig,
// type: signer.signatureType,
// };

const byteChallenge = hexToBytes(msgChallenge);
const base64Challenge = bytesToBase64(byteChallenge); // Challenge needs to be Base64 in the message

const res = {
signature: privateSignature,
// signature: privateSignature,
signature: sig,
challenge: base64Challenge,
};
return res;
Expand Down
15 changes: 13 additions & 2 deletions src/client/kwil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,18 @@ export abstract class Kwil<T extends EnvironmentType> extends Client {
params: encodedParams,
};

// TODO: Add support for signer
// TODO: Add support for signer if in private mode

// if (kwilSigner && this.authMode === AuthenticationMode.PRIVATE) {
// const authBody = await this.handleAuthenticatePrivate(actionBody, kwilSigner);
// const message = await this.buildMessage(
// actionBody,
// kwilSigner,
// authBody.challenge,
// authBody.signature
// );
// return await this.callClient(message);
// }

return await this.selectQueryClient(q);
}
Expand Down Expand Up @@ -492,7 +503,7 @@ export abstract class Kwil<T extends EnvironmentType> extends Client {
actionBody: ActionBody,
kwilSigner?: KwilSigner,
challenge?: string,
signature?: Signature<BytesEncodingStatus.BASE64_ENCODED>
signature?: BytesEncodingStatus.BASE64_ENCODED
): Promise<Message> {
if (!actionBody.name) {
throw new Error('name is required in actionBody');
Expand Down
20 changes: 8 additions & 12 deletions src/core/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export interface MsgReceipt {
/**
* `MsgData` is the interface for a payload structure for a request to the Kwil `call` GRPC endpoint {@link https://github.com/kwilteam/proto/blob/main/kwil/tx/v1/call.proto}.
*/

export interface MsgData<T extends PayloadBytesTypes> {
body: MsgBody<T>;
auth_type: AnySignatureType;
// when the other bytes are base64, it means it is time to turn the sender bytes to hex string
sender: Nillable<T extends BytesEncodingStatus.BASE64_ENCODED ? HexString : Uint8Array>;
signature: Nillable<Signature<BytesEncodingStatus.BASE64_ENCODED>>;
signature: Nillable<T extends BytesEncodingStatus.BASE64_ENCODED ? Base64String : Uint8Array>;
}

interface MsgBody<T extends PayloadBytesTypes> {
Expand Down Expand Up @@ -63,10 +63,7 @@ export class BaseMessage<T extends PayloadBytesTypes> implements MsgData<T> {
},
auth_type: SignatureType.SECP256K1_PERSONAL,
sender: null,
signature: {
sig: '',
type: SignatureType.SECP256K1_PERSONAL,
},
signature: null,
};
}

Expand All @@ -79,12 +76,14 @@ export class BaseMessage<T extends PayloadBytesTypes> implements MsgData<T> {
}

public get sender(): Nillable<
T extends BytesEncodingStatus.BASE64_ENCODED ? Base64String : Uint8Array
T extends BytesEncodingStatus.BASE64_ENCODED ? HexString : Uint8Array
> {
return this.data.sender;
}

public get signature(): Nillable<Signature<BytesEncodingStatus.BASE64_ENCODED>> {
public get signature(): Nillable<
T extends BytesEncodingStatus.BASE64_ENCODED ? Base64String : Uint8Array
> {
return this.data.signature;
}
}
Expand All @@ -110,10 +109,7 @@ export namespace Msg {
},
auth_type: SignatureType.SECP256K1_PERSONAL,
sender: null,
signature: {
sig: '',
type: SignatureType.SECP256K1_PERSONAL,
},
signature: null,
};

// Pass the 'msg' object to the 'configure' function allowing external modification of its propoerties before instantiation of BaseMessage.
Expand Down
3 changes: 2 additions & 1 deletion src/core/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export type CustomSigner = NonNil<(message: Uint8Array) => Promise<Uint8Array>>;
export type SignerSupplier = Promisy<EthSigner | CustomSigner>;

export interface AuthBody {
signature: Signature<BytesEncodingStatus.BASE64_ENCODED>;
// signature: Signature<BytesEncodingStatus.BASE64_ENCODED>;
signature: BytesEncodingStatus.BASE64_ENCODED;
challenge: HexString;
}

Expand Down
5 changes: 3 additions & 2 deletions src/message/payloadMsg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { AnySignatureType, Signature, SignatureType } from '../core/signature';
import { objects } from '../utils/objects';
import { bytesToHex } from '../utils/serial';
import { encodeActionCall } from '../utils/kwilEncoding';
// import { base64ToBytes } from '../utils/serial';

export interface PayloadMsgOptions {
challenge: string;
signatureType: AnySignatureType;
identifier: Uint8Array;
signer: SignerSupplier;
signature: Signature<BytesEncodingStatus.BASE64_ENCODED>;
signature: BytesEncodingStatus.BASE64_ENCODED;
}

/**
Expand All @@ -24,7 +25,7 @@ export class PayloadMsg {
public signatureType: AnySignatureType;
public identifier: Uint8Array;
public signer: SignerSupplier;
public signature: Signature<BytesEncodingStatus.BASE64_ENCODED>;
public signature: BytesEncodingStatus.BASE64_ENCODED;

/**
* Initializes a new `PayloadMsg` instance.
Expand Down
Loading

0 comments on commit 5269c52

Please sign in to comment.