Skip to content

Commit

Permalink
Merge pull request #15 from bob-collective/feat/sign-message
Browse files Browse the repository at this point in the history
feat: sign message
  • Loading branch information
gregdhill committed Mar 25, 2024
2 parents e08f6c3 + fc309bf commit a4af39d
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 41 deletions.
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/bob-collective/bob-snap"
},
"source": {
"shasum": "3gkQ5n9BJXvJnuTZ0KobpTgDQIP4Pq+sp0ZyEipYn2o=",
"shasum": "Ea+2ea/YTw965c+UA8zhzQIQ8CWIV3yEt5llK6YUjkE=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
65 changes: 32 additions & 33 deletions packages/snap/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { onRpcRequest } from '../index';
import { getExtendedPublicKey, signPsbt, getMasterFingerprint, manageNetwork, validateRequest, saveLNDataToSnap, signLNInvoice, getLNDataFromSnap } from '../rpc';
import { getExtendedPublicKey, signPsbt, getMasterFingerprint, manageNetwork, getAllXpubs, saveLNDataToSnap, signLNInvoice, getLNDataFromSnap } from '../rpc';
import { BitcoinNetwork, KeyOptions, ScriptType } from '../interface';
import { SnapMock } from '../rpc/__mocks__/snap';
import { LNDataToSnap } from '../rpc/__tests__/fixtures/bitcoinNode';
Expand All @@ -18,7 +18,8 @@ jest.mock('../rpc', () => {
validateRequest,
saveLNDataToSnap: jest.fn(),
signLNInvoice: jest.fn(),
getLNDataFromSnap: jest.fn()
getLNDataFromSnap: jest.fn(),
getAllXpubs: jest.fn(),
};
});

Expand All @@ -37,33 +38,19 @@ describe('index', () => {
describe('validateRequest', () => {
it('should throw error when given network not match for getExtendedPublicKey', async () => {
await expect(onRpcRequest({
origin: 'origin',
request: {
method: 'btc_getPublicExtendedKey',
params: {
network: BitcoinNetwork.Main,
scriptType: ScriptType.P2PKH,
},
},
}),
).rejects.toThrowError('Network not match');
});

it('should throw error when given network not match for signPsbt', async () => {
await expect(onRpcRequest({
origin: 'origin',
request: {
method: 'btc_getPublicExtendedKey',
params: {
network: BitcoinNetwork.Main,
scriptType: ScriptType.P2PKH,
},
origin: 'origin',
request: {
method: 'btc_getPublicExtendedKey',
params: {
network: BitcoinNetwork.Main,
scriptType: ScriptType.P2PKH,
},
}),
},
}),
).rejects.toThrowError('Network not match');
});

it('should throw error if domain not allowed', async() => {
it('should throw error if domain not allowed', async () => {
await expect(onRpcRequest({
origin: 'origin',
request: {
Expand All @@ -74,11 +61,11 @@ describe('index', () => {
},
},
}),
).rejects.toThrowError('Domain not allowed');
).rejects.toThrowError('Domain not allowed');
})
});

describe('rpc methods', function() {
describe('rpc methods', function () {
it('should call getExtendedPublicKey when method btc_getPublicExtendedKey get called', async () => {
await onRpcRequest({
origin: domain,
Expand All @@ -94,6 +81,18 @@ describe('index', () => {
await expect(getExtendedPublicKey).toBeCalled();
});

it('should getAllXpubs', async () => {
await onRpcRequest({
origin: domain,
request: {
method: 'btc_getAllXpubs',
params: {},
},
});

await expect(getAllXpubs).toBeCalled();
});

it('should sign PSBT when method btc_signPSBT get called', async () => {
await onRpcRequest({
origin: domain,
Expand Down Expand Up @@ -182,12 +181,12 @@ describe('index', () => {

it('should throw error when given method not exist', async () => {
await expect(onRpcRequest({
origin: domain,
request: {
method: 'btc_method_not_exist' as any,
params: {} as any,
},
}),
origin: domain,
request: {
method: 'btc_method_not_exist' as any,
params: {} as any,
},
}),
).rejects.toThrowError('Method not found.');
});
});
Expand Down
3 changes: 3 additions & 0 deletions packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
saveLNDataToSnap,
getLNDataFromSnap,
signLNInvoice,
signMessage,
} from './rpc';
import { SnapError, RequestErrors } from './errors';
import * as ecc from "@bitcoin-js/tiny-secp256k1-asmjs";
Expand Down Expand Up @@ -92,6 +93,8 @@ export const onRpcRequest = async ({ origin, request }: RpcRequest) => {
);
case 'btc_signLNInvoice':
return signLNInvoice(origin, snap, request.params.invoice);
case 'btc_signMessage':
return signMessage(origin, snap, request.params.message, request.params.hdPath);
default:
throw SnapError.of(RequestErrors.MethodNotSupport);
}
Expand Down
11 changes: 10 additions & 1 deletion packages/snap/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ export interface SignLNInvoice {
};
}

export interface SignMessage {
method: 'btc_signMessage';
params: {
message: string;
hdPath: string;
};
}

export type MetamaskBTCRpcRequest =
| GetAllXpubsRequest
| GetPublicExtendedKeyRequest
Expand All @@ -87,7 +95,8 @@ export type MetamaskBTCRpcRequest =
| ManageNetwork
| SaveLNDataToSnap
| GetLNDataFromSnap
| SignLNInvoice;
| SignLNInvoice
| SignMessage;

export type BTCMethodCallback = (
originString: string,
Expand Down
17 changes: 12 additions & 5 deletions packages/snap/src/rpc/__tests__/fixtures/bitcoinNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,20 @@ export const LNDataFromSnap = {
}

export const LNDataToSnap = {
domain : 'www.justsnap.io',
walletId: "id00000001",
credential: "testAdmin:123456",
password: "testPassword",
invoice: "lnbc100u1p34k6pppp5332v7z238qt7jrhjz5mkhckdx2uuc50d8xzpfyanj8p3plav9z5sdq8w3jhxaqcqzpgxqyz5vqsp5stj40j57779ahamqp9p3rpq0eudt75f9kxw7yyhuwwaxfmuqsqzq9qyyssqqudc8qc5np9rj5ypn6p9jlafn5sc02nwp60at38cwem4ycz9p9pqdlknk5k3yfayh3pzhndjt2gev8g4rqtnr6art5cagr2c0f3xkxqqfx27k5"
domain: 'www.justsnap.io',
walletId: "id00000001",
credential: "testAdmin:123456",
password: "testPassword",
invoice: "lnbc100u1p34k6pppp5332v7z238qt7jrhjz5mkhckdx2uuc50d8xzpfyanj8p3plav9z5sdq8w3jhxaqcqzpgxqyz5vqsp5stj40j57779ahamqp9p3rpq0eudt75f9kxw7yyhuwwaxfmuqsqzq9qyyssqqudc8qc5np9rj5ypn6p9jlafn5sc02nwp60at38cwem4ycz9p9pqdlknk5k3yfayh3pzhndjt2gev8g4rqtnr6art5cagr2c0f3xkxqqfx27k5"
}

export const LNSignature = {
signature: "1f9b311f576424fe87c769ab9146f6b3613399b0b54f13b781639b2bc3f40e22706012192cdcdaa9830b5a21494e54740cfcec3b1e7f75d35e1c9afeb143d5c1ed"
}

export const MessageAndSig = {
domain: 'www.justsnap.io',
message: "Hello World!",
hdPath: `m/84'/0'/0'/0/0`,
signature: "20d222a9945150cf3b3bb46eb3a3d333a5c0a91cd015bfcbceec0f3d612997fe81413b595ee7b3d3e50d9fb3acd8f852c428be06fc3979cbda77e8b73040b3da17"
}
26 changes: 26 additions & 0 deletions packages/snap/src/rpc/__tests__/signMessage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { signMessage } from '../signMessage';
import { SnapMock } from '../__mocks__/snap';
import { bip44, MessageAndSig } from "./fixtures/bitcoinNode";

describe('signLNInvoice', () => {
const snapStub = new SnapMock();

afterEach(() => {
snapStub.reset()
})

it('should return signature for message', async () => {
snapStub.rpcStubs.snap_dialog.mockResolvedValue(true);
snapStub.rpcStubs.snap_getBip32Entropy.mockResolvedValue(bip44.slip10Node);
const signature = await signMessage(MessageAndSig.domain, snapStub, MessageAndSig.message, MessageAndSig.hdPath);
expect(signature).toBe(MessageAndSig.signature);
})

it('should reject the sign request and throw error if user reject the sign the lightning invoice', async () => {
snapStub.rpcStubs.snap_dialog.mockResolvedValue(false);

await expect(signMessage(MessageAndSig.domain, snapStub, MessageAndSig.message, MessageAndSig.hdPath))
.rejects
.toThrowError('User reject the sign request');
})
});
2 changes: 1 addition & 1 deletion packages/snap/src/rpc/getExtendedPublicKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function extractAccountPrivateKey(snap: Snap, network: Network, scr
path,
curve: CRYPTO_CURVE
},
}) as SLIP10Node
}) as SLIP10Node;

const privateKeyBuffer = Buffer.from(trimHexPrefix(slip10Node.privateKey), "hex")
const chainCodeBuffer = Buffer.from(trimHexPrefix(slip10Node.chainCode), "hex")
Expand Down
1 change: 1 addition & 0 deletions packages/snap/src/rpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { saveLNDataToSnap } from './saveLNDataToSnap';
export { getLNDataFromSnap } from './getLNDataFromSnap';
export { signLNInvoice } from './signLNInvoice';
export { signInput } from './signInput';
export { signMessage } from './signMessage';
35 changes: 35 additions & 0 deletions packages/snap/src/rpc/signMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Snap } from '../interface';
import { getHDNode } from '../utils/getHDNode';
import bitcoinMessage from 'bitcoinjs-message';
import { RequestErrors, SnapError } from '../errors';
import { divider, heading, panel, text } from "@metamask/snaps-ui";

export async function signMessage(
domain: string,
snap: Snap,
message: string,
hdPath: string,
): Promise<string> {
const result = await snap.request({
method: 'snap_dialog',
params: {
type: 'confirmation',
content: panel([
heading('Sign Message'),
text(`Please sign this message from ${domain}`),
divider(),
text(`${message}`),
]),
},
});

if (result) {
const privateKey = (await getHDNode(snap, hdPath)).privateKey;
const signature = bitcoinMessage
.sign(message, privateKey, true)
.toString('hex');
return signature;
} else {
throw SnapError.of(RequestErrors.RejectSign);
}
}

0 comments on commit a4af39d

Please sign in to comment.