Skip to content
Open
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
3 changes: 3 additions & 0 deletions packages/snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"dependencies": {
"@chainsafe/webzjs-keys": "0.1.0",
"@metamask/snaps-sdk": "^6.17.1",
"@noble/hashes": "^1.4.0",
"@noble/secp256k1": "^2.1.0",
"bs58check": "^4.0.0",
"buffer": "^6.0.3",
"superstruct": "^2.0.2"
},
Expand Down
16 changes: 15 additions & 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/ChainSafe/WebZjs.git"
},
"source": {
"shasum": "wTPZl0jiwxyFr3cUhss5/b0WjFGdZP9y/VIPafGtDAM=",
"shasum": "gR+0fkxHpNURVXHOtwC42+XPZys6n8nDX6ZdyQ3bwpo=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand All @@ -31,6 +31,20 @@
"coinType": 133
}
],
"snap_getBip32Entropy": [
{
"path": ["m", "48'", "133'"],
"curve": "secp256k1"
},
{
"path": ["m", "48'", "133'", "0'", "133000'", "0", "0"],
"curve": "secp256k1"
},
{
"path": ["m", "48'", "1'", "0'", "133000'", "0", "0"],
"curve": "secp256k1"
}
],
"snap_manageState": {}
},
"platformVersion": "6.17.1",
Expand Down
40 changes: 37 additions & 3 deletions packages/snap/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,21 @@ import {
} from '@metamask/snaps-sdk';
import { setBirthdayBlock } from './rpc/setBirthdayBlock';
import { getSnapState } from './rpc/getSnapState';
import { SetBirthdayBlockParams, SignPcztParams, SnapState } from './types';
import {
SetBirthdayBlockParams,
SignPcztParams,
SignTransparentMessageParams,
SignTransparentParams,
SnapState,
TransparentPublicKeyParams,
} from './types';
import { setSnapState } from './rpc/setSnapState';
import { signPczt } from './rpc/signPczt'
import { signPczt } from './rpc/signPczt';
import { signTransparent } from './rpc/signTransparent';
import { getTransparentPublicKey } from './rpc/getTransparentPublicKey';
import { signTransparentMessage } from './rpc/signTransparentMessage';

import { assert, object, number, optional, string } from 'superstruct';
import { assert, object, number, optional, string, array } from 'superstruct';
import { getSeedFingerprint } from './rpc/getSeedFingerprint';
import type { OnInstallHandler } from "@metamask/snaps-sdk";
import { installDialog } from './utils/dialogs';
Expand Down Expand Up @@ -45,6 +55,30 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request, origin }) =>
}),
}));
return await signPczt(request.params as SignPcztParams, origin);
case 'signTransparent':
assert(request.params, object({
derivationPath: string(),
sighashes: array(string()),
details: object({
toAddress: string(),
amount: string(),
network: string()
})
}));
return await signTransparent(request.params as SignTransparentParams, origin);
case 'signTransparentMessage':
assert(request.params, object({
derivationPath: string(),
message: string(),
network: optional(string()),
expectedAddress: optional(string())
}));
return await signTransparentMessage(request.params as SignTransparentMessageParams, origin);
case 'getTransparentPublicKey':
assert(request.params, object({
derivationPath: string()
}));
return await getTransparentPublicKey(request.params as TransparentPublicKeyParams, origin);
case 'getSeedFingerprint':
return await getSeedFingerprint();
case 'setBirthdayBlock':
Expand Down
34 changes: 34 additions & 0 deletions packages/snap/src/rpc/getTransparentPublicKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Box, Text } from '@metamask/snaps-sdk/jsx';
import { ensureDerivationPath, requestPrivateKey, splitDerivationPath } from '../utils/derivation';
import { getPublicKey } from '@noble/secp256k1';
import { snapConfirm } from '../utils/dialogs';

export async function getTransparentPublicKey({ derivationPath }: { derivationPath: string }, origin: string): Promise<string> {
ensureDerivationPath(derivationPath);

const confirmed = await snapConfirm({
title: 'Access to ZIP-48 public key',
prompt: (
<Box>
<Text>Origin: {origin}</Text>
<Text>Derivation path: {derivationPath}</Text>
</Box>
)
});

if (!confirmed) {
throw new Error('User rejected the request');
}

const pathSegments = splitDerivationPath(derivationPath);
const privateKey = await requestPrivateKey(pathSegments);
const publicKey = getPublicKey(privateKey, true);
return uint8ArrayToHex(publicKey);
}

function uint8ArrayToHex(bytes: Uint8Array): string {
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}

116 changes: 116 additions & 0 deletions packages/snap/src/rpc/signTransparent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Box, Divider, Heading, Text } from '@metamask/snaps-sdk/jsx';
import { SignTransparentParams } from '../types';
import { hexStringToUint8Array } from '../utils/hexStringToUint8Array';
import { snapConfirm } from '../utils/dialogs';
import { assert, array, object, optional, string } from 'superstruct';
import { Signature, sign } from '@noble/secp256k1';
import { ensureDerivationPath, requestPrivateKey, splitDerivationPath } from '../utils/derivation';
import { ensureHmacSupport } from '../utils/secp256k1';

const SIGHASH_ALL = 0x01;

const SignTransparentStruct = object({
derivationPath: string(),
sighashes: array(string()),
details: object({
toAddress: string(),
amount: string(),
network: string()
}),
metadata: optional(
object({
redeemScript: optional(string())
})
)
});

export async function signTransparent(params: SignTransparentParams, origin: string): Promise<string[]> {
ensureHmacSupport();
assert(params, SignTransparentStruct);
ensureDerivationPath(params.derivationPath);

const confirmed = await snapConfirm({
title: 'Transparent transaction signing',
prompt: (
<Box>
<Heading>Transparent Multisig</Heading>
<Divider />
<Text>Origin: {origin}</Text>
<Text>Recipient: {params.details.toAddress}</Text>
<Text>Amount: {params.details.amount} ZEC</Text>
<Text>Network: {params.details.network}</Text>
</Box>
)
});

if (!confirmed) {
throw new Error('User rejected signing');
}

const derivationPathSegments = splitDerivationPath(params.derivationPath);
const privateKey = await requestPrivateKey(derivationPathSegments);

const signatures = params.sighashes.map((hashHex) => {
if (typeof hashHex !== 'string' || hashHex.length !== 64) {
throw new Error('Invalid sighash');
}
const digest = hexStringToUint8Array(hashHex);
const signature = sign(digest, privateKey);
const derSignature = signatureToDer(signature);
const fullSignature = new Uint8Array(derSignature.length + 1);
fullSignature.set(derSignature, 0);
fullSignature[derSignature.length] = SIGHASH_ALL;
return uint8ArrayToHex(fullSignature);
});

return signatures;
}

function uint8ArrayToHex(bytes: Uint8Array): string {
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}

function signatureToDer(signature: Signature): Uint8Array {
const r = normalizeDerComponent(signature.r);
const s = normalizeDerComponent(signature.s);
const length = 2 + r.length + 2 + s.length;
const result = new Uint8Array(2 + length);
let offset = 0;
result[offset++] = 0x30;
result[offset++] = length;
result[offset++] = 0x02;
result[offset++] = r.length;
result.set(r, offset);
offset += r.length;
result[offset++] = 0x02;
result[offset++] = s.length;
result.set(s, offset);
return result;
}

function normalizeDerComponent(value: bigint): Uint8Array {
let hex = value.toString(16);
if (hex.length % 2 !== 0) {
hex = `0${hex}`;
}
const bytes = hexStringToUint8Array(hex);
let sliceIndex = 0;
while (
sliceIndex < bytes.length - 1 &&
bytes[sliceIndex] === 0x00 &&
(bytes[sliceIndex + 1] & 0x80) === 0
) {
sliceIndex += 1;
}
const trimmed = bytes.slice(sliceIndex);
if ((trimmed[0] & 0x80) !== 0) {
const prefixed = new Uint8Array(trimmed.length + 1);
prefixed[0] = 0x00;
prefixed.set(trimmed, 1);
return prefixed;
}
return trimmed;
}

Loading