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

agent name service v2 integration #350

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions packages/background/src/ans/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ROUTE = "ans";
58 changes: 58 additions & 0 deletions packages/background/src/ans/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Env, Handler, InternalHandler, Message } from "@keplr-wallet/router";
import {
PubKeyPayload,
SignPayload,
MakeVerificationStringPayload,
} from "./messages";
import { NameService } from "./service";

export const getNameServiceHandler: (service: NameService) => Handler = (
service
) => {
return (env: Env, msg: Message<unknown>) => {
switch (msg.constructor) {
case SignPayload:
return handleSignPayload(service)(env, msg as SignPayload);
case PubKeyPayload:
return handleLookupPubKey(service)(env, msg as PubKeyPayload);
case MakeVerificationStringPayload:
return handleMakeVerificationString(service)(
env,
msg as MakeVerificationStringPayload
);
default:
throw new Error("Unknown msg type");
}
};
};

const handleSignPayload: (
service: NameService
) => InternalHandler<SignPayload> = (service) => {
return async (env, msg) => {
const signature = await service.signDomain(env, msg.chainId, msg.digest);
return signature;
};
};

const handleLookupPubKey: (
service: NameService
) => InternalHandler<PubKeyPayload> = (service) => {
return async (env, msg) => {
const pubKey = await service.getPubKey(env, msg.chainId);
return pubKey;
};
};

const handleMakeVerificationString: (
service: NameService
) => InternalHandler<MakeVerificationStringPayload> = (service) => {
return async (env, msg) => {
const verificationString = await service.makeVerificationString(
env,
msg.signature,
msg.chainId
);
return verificationString;
};
};
2 changes: 2 additions & 0 deletions packages/background/src/ans/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./messages";
export * from "./service";
16 changes: 16 additions & 0 deletions packages/background/src/ans/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Router } from "@keplr-wallet/router";
import { ROUTE } from "./constants";
import { NameService } from "./service";
import {
PubKeyPayload,
SignPayload,
MakeVerificationStringPayload,
} from "./messages";
import { getNameServiceHandler } from "./handler";

export function init(router: Router, service: NameService): void {
router.registerMessage(SignPayload);
router.registerMessage(PubKeyPayload);
router.registerMessage(MakeVerificationStringPayload);
router.addHandler(ROUTE, getNameServiceHandler(service));
}
2 changes: 2 additions & 0 deletions packages/background/src/ans/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./service";
export * from "./init";
83 changes: 83 additions & 0 deletions packages/background/src/ans/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Message } from "@keplr-wallet/router";
import { ROUTE } from "./constants";

export class SignPayload extends Message<string> {
public static type() {
return "sign-payload";
}

constructor(
public readonly chainId: string,
public readonly digest: Uint8Array
) {
super();
}

validateBasic(): void {
if (!this.chainId) {
throw new Error("Chain id is empty");
}
}

route(): string {
return ROUTE;
}

type(): string {
return SignPayload.type();
}
}

export class PubKeyPayload extends Message<Uint8Array> {
public static type() {
return "lookup-pub-key";
}

constructor(public readonly chainId: string) {
super();
}

validateBasic(): void {
if (!this.chainId) {
throw new Error("Chain id is empty");
}
}

route(): string {
return ROUTE;
}

type(): string {
return PubKeyPayload.type();
}
}

export class MakeVerificationStringPayload extends Message<string> {
public static type() {
return "make-verification-string-payload";
}

constructor(
public readonly chainId: string,
public readonly signature: Buffer
) {
super();
}

validateBasic(): void {
if (!this.chainId) {
throw new Error("Chain id is empty");
}
if (!this.signature) {
throw new Error("Signature is empty");
}
}

route(): string {
return ROUTE;
}

type(): string {
return MakeVerificationStringPayload.type();
}
}
96 changes: 96 additions & 0 deletions packages/background/src/ans/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { toBase64, toBech32 } from "@cosmjs/encoding";
import { Hash, PrivKeySecp256k1 } from "@keplr-wallet/crypto";
import { Env } from "@keplr-wallet/router";
import { KeyRingService } from "../keyring";

export class NameService {
protected keyRingService!: KeyRingService;

async init(keyRingService: KeyRingService) {
this.keyRingService = keyRingService;
}
/**
* Sign the payload
*
* @param env The extension environment
* @param chainId The target chain id
* @param digest The Uint8Array digest that should be signed
* @returns The bech32 encoded signature for the verification
*/
public async signDigest(
env: Env,
chainId: string,
digest: Uint8Array
): Promise<string> {
const sk = await this.getPrivateKey(env, chainId);
const privateKey = new PrivKeySecp256k1(sk);
// sign the payload
const rawSignature = privateKey.signDigest32(digest);
return toBech32("sig", rawSignature, 1000);
}

async signDomain(env: Env, chainId: string, digest: any): Promise<string> {
const sk = await this.getPrivateKey(env, chainId);
const privateKey = new PrivKeySecp256k1(sk);
// sign the payload
const rawSignature = privateKey.signDigest32(digest);
// convert and return the signature
return toBase64(rawSignature);
}

/**
* Builds a private key from the signature of the current keychain
*
* @param env The environment of the extension
* @param chainId The target chain id
* @returns The generated private key object
* @private
*/
protected async getPrivateKey(
env: Env,
chainId: string
): Promise<Uint8Array> {
return Hash.sha256(
Buffer.from(
await this.keyRingService.sign(
env,
chainId,
Buffer.from(
JSON.stringify({
account_number: 0,
chain_id: chainId,
fee: [],
memo: "Create Agent Name Service Secret encryption key. Only approve requests by Keplr.",
msgs: [],
sequence: 0,
})
)
)
)
);
}

public async getPubKey(env: Env, chainId: string): Promise<Uint8Array> {
const sk = await this.getPrivateKey(env, chainId);
const privateKey = new PrivKeySecp256k1(sk);
const pubKey = privateKey.getPubKey().toBytes();
return pubKey;
}

public async makeVerificationString(
env: Env,
domain: Buffer,
chainId: string
): Promise<string> {
const signature = await this.signDomain(env, chainId, domain);
const signatureBuffer = Buffer.from(signature, "base64"); // Example first bytes
const pubKey: any = await this.getPubKey(env, chainId);
const pubkeyBuffer = Buffer.from(pubKey, "base64");
const data = Buffer.concat([pubkeyBuffer, signatureBuffer]);
return data
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
}
4 changes: 4 additions & 0 deletions packages/background/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ChainInfo } from "@keplr-wallet/types";
import { CommonCrypto } from "./keyring";
import { Notification } from "./tx";
import { LedgerOptions } from "./ledger/options";
import * as AgentNameService from "./ans/internal";

export * from "./persistent-memory";
export * from "./chains";
Expand Down Expand Up @@ -138,6 +139,7 @@ export function init(
const umbralService = new Umbral.UmbralService(chainsService);

const messagingService = new Messaging.MessagingService();
const nameService = new AgentNameService.NameService();

Interaction.init(router, interactionService);
PersistentMemory.init(router, persistentMemoryService);
Expand All @@ -155,6 +157,7 @@ export function init(

Umbral.init(router, umbralService);
Messaging.init(router, messagingService);
AgentNameService.init(router, nameService);

return {
initFn: async () => {
Expand Down Expand Up @@ -191,6 +194,7 @@ export function init(
await analyticsService.init();
await umbralService.init(keyRingService, permissionService);
await messagingService.init(keyRingService);
await nameService.init(keyRingService);
},
};
}
1 change: 1 addition & 0 deletions packages/fetch-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
"@types/classnames": "^2.3.1",
"@types/color": "^3.0.3",
"@types/debounce": "^1.2.1",
"@types/eslint": "^8",
"@types/firefox-webext-browser": "^70.0.1",
"@types/js-yaml": "^4",
"@types/react-html-parser": "^2.0.2",
Expand Down
49 changes: 49 additions & 0 deletions packages/fetch-extension/src/components/ans-view/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from "react";
import { FunctionComponent } from "react";
import style from "./style.module.scss";
import classnames from "classnames";
import { FormattedMessage } from "react-intl";
import { Button } from "reactstrap";
import { useNavigate } from "react-router";

export const ANSView: FunctionComponent = () => {
const navigate = useNavigate();
return (
<div className={style["containerInner"]}>
<div className={style["vertical"]}>
<p
className={classnames(
"h2",
"my-0",
"font-weight-normal",
style["paragraphMain"]
)}
>
<FormattedMessage id="main.ans.title" />
</p>
<p
className={classnames(
"h4",
"my-0",
"font-weight-normal",
style["paragraphSub"]
)}
>
<FormattedMessage id="main.fns.paragraph" />
</p>
</div>
<div style={{ flex: 1 }} />

<Button
className={style["button"]}
color="primary"
size="sm"
onClick={() => {
navigate("/agent-name-service");
}}
>
<FormattedMessage id="main.fns.button" />
</Button>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@import "../../styles/var";

.containerInner {
display: flex;
flex-direction: row;

height: 48px;
justify-content: center;
align-items: center;

.paragraphMain {
line-height: 1.35;
}

.paragraphSub {
line-height: 1.35;
color: #8898aa;
}

.vertical {
display: flex;
flex-direction: column;
}

.button {
min-width: 76px;
padding: 6px 10px 4px;
}
}
Loading
Loading