diff --git a/packages/site/src/components/Sovereign.tsx b/packages/site/src/components/Sovereign.tsx
index 3b62f93..befb533 100644
--- a/packages/site/src/components/Sovereign.tsx
+++ b/packages/site/src/components/Sovereign.tsx
@@ -17,6 +17,7 @@ type SovereignState = {
curve: CurveSelectorState;
keyId: number;
message?: string;
+ nonce?: number;
request?: string;
response?: string;
};
@@ -27,6 +28,7 @@ export const Sovereign = () => {
curve: `secp256k1`,
keyId: 0,
message: 'Some signature message...',
+ nonce: 0,
};
const [state, setState] = useState(initialState);
@@ -97,10 +99,29 @@ export const Sovereign = () => {
cols={40}
/>
+
Nonce:
+
+ {
+ const { value } = ev.target;
+
+ // Allow only positive integers (whole numbers greater than or equal to zero)
+ const regex = /^[0-9\b]+$/u; // Allows digits only
+ if (value === '' || regex.test(value)) {
+ setState({
+ ...state,
+ nonce: parseInt(value, 10),
+ });
+ }
+ }}
+ />
+
{
- const { method, curve, keyId, message } = state;
+ const { method, curve, keyId, message, nonce } = state;
const path = ['m', "44'", "1551'"];
path.push(keyId.toString());
@@ -114,6 +135,7 @@ export const Sovereign = () => {
transaction: {
message: message || '',
},
+ nonce,
};
} else {
params = {
diff --git a/packages/snap/README.md b/packages/snap/README.md
index c508bef..f08af8e 100644
--- a/packages/snap/README.md
+++ b/packages/snap/README.md
@@ -45,6 +45,7 @@ Will emit a confirmation dialog for the user.
- `curve`: The curve of the public key (`secp256k1` or `ed25519`).
- `schema`: A [borsh](https://www.npmjs.com/package/borsh) schema for the transaction.
- `transaction`: A transaction to be serialized using the provided schema. The signature will be performed over the serialized transaction.
+- `nonce`: An unsigned 64-bits number to be appended to the serialized transaction.
##### Example
@@ -90,12 +91,13 @@ const response = request({
amount: 1582,
},
},
+ nonce: 0,
},
});
if (
!response ===
- '0xfd2e4b23a3e3f498664af355b341e833324276270a13f9647dd1f043248f92fccaa037d4cfc9d23f13a295f7d505ee13afb2b10cea548890678f9002947cbb0a'
+ '0xe563b3d772572e57bff43e31e449dbc8e98f7580fea10b2ad1ad8278b3edcf5ff5b3a24b85cef3192fcd70452c58e7c968e500da9e290aefadf5ef22a4efbf0d',
) {
throw new Error('Invalid signature');
}
diff --git a/packages/snap/package.json b/packages/snap/package.json
index ee36796..6c696ba 100644
--- a/packages/snap/package.json
+++ b/packages/snap/package.json
@@ -34,7 +34,8 @@
"@noble/ed25519": "^1.6.0",
"@noble/secp256k1": "^1.7.1",
"borsh": "^1.0.0",
- "buffer": "^6.0.3"
+ "buffer": "^6.0.3",
+ "int64-buffer": "^1.0.1"
},
"devDependencies": {
"@jest/globals": "^29.5.0",
diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json
index 96455b2..fd05677 100644
--- a/packages/snap/snap.manifest.json
+++ b/packages/snap/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/Sovereign-Labs/sov-snap.git"
},
"source": {
- "shasum": "WZco6V2pvJMpitcOysOcWYoVpGTBAn7cSdHRGM6MRu0=",
+ "shasum": "wOP1ZGD2ciBIysiXTThBibt0cTkaxNKbsFCLXk+Si/c=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
diff --git a/packages/snap/src/index.test.ts b/packages/snap/src/index.test.ts
index 646e5a3..9c9db92 100644
--- a/packages/snap/src/index.test.ts
+++ b/packages/snap/src/index.test.ts
@@ -84,6 +84,7 @@ describe('onRpcRequest', () => {
payload: Array(176).fill(5),
},
},
+ nonce: 26,
},
});
@@ -114,6 +115,7 @@ describe('onRpcRequest', () => {
amount: 1582,
},
},
+ nonce: 0,
},
});
@@ -122,7 +124,7 @@ describe('onRpcRequest', () => {
await ui.ok();
expect(await response).toRespondWith(
- '0xfd2e4b23a3e3f498664af355b341e833324276270a13f9647dd1f043248f92fccaa037d4cfc9d23f13a295f7d505ee13afb2b10cea548890678f9002947cbb0a',
+ '0xe563b3d772572e57bff43e31e449dbc8e98f7580fea10b2ad1ad8278b3edcf5ff5b3a24b85cef3192fcd70452c58e7c968e500da9e290aefadf5ef22a4efbf0d',
);
await close();
diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts
index d61f5ce..2d0230f 100644
--- a/packages/snap/src/index.ts
+++ b/packages/snap/src/index.ts
@@ -6,6 +6,7 @@ import { add0x, assert, bytesToHex, remove0x } from '@metamask/utils';
import { sign as signEd25519 } from '@noble/ed25519';
import { sign as signSecp256k1 } from '@noble/secp256k1';
import { serialize } from 'borsh';
+import { Uint64LE } from 'int64-buffer';
import type { GetBip32PublicKeyParams, SignTransactionParams } from './types';
@@ -28,9 +29,11 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
});
case 'signTransaction': {
- const { schema, transaction, curve, ...params } =
+ const { schema, transaction, nonce, curve, ...params } =
request.params as SignTransactionParams;
+ const nonceLe = new Uint64LE(nonce);
+ const nonceBytes = nonceLe.toBuffer();
const serializedTransaction = serialize(schema, transaction);
const json = await snap.request({
@@ -58,11 +61,11 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
type: DialogType.Confirmation,
content: panel([
heading('Signature request'),
- text(
- `Do you want to ${curve} sign ${JSON.stringify(
- transaction,
- )} with the following public key?`,
- ),
+ text(`Do you want to ${curve} sign`),
+ copyable(JSON.stringify(transaction)),
+ text(`with nonce`),
+ copyable(String(nonce)),
+ text(`and the following public key?`),
copyable(add0x(node.publicKey)),
]),
},
@@ -73,14 +76,18 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
}
const privateKey = remove0x(node.privateKey);
+ const signedTransaction = new Uint8Array([
+ ...serializedTransaction,
+ ...nonceBytes,
+ ]);
let signed;
switch (curve) {
case 'ed25519':
- signed = await signEd25519(serializedTransaction, privateKey);
+ signed = await signEd25519(signedTransaction, privateKey);
break;
case 'secp256k1':
- signed = await signSecp256k1(serializedTransaction, privateKey);
+ signed = await signSecp256k1(signedTransaction, privateKey);
break;
default:
throw new Error(`Unsupported curve: ${String(curve)}.`);
diff --git a/packages/snap/src/types.ts b/packages/snap/src/types.ts
index 1b2bffb..0bb5bd5 100644
--- a/packages/snap/src/types.ts
+++ b/packages/snap/src/types.ts
@@ -45,6 +45,11 @@ export type SignTransactionParams = {
*/
transaction: any;
+ /**
+ * The nonce of the transaction.
+ */
+ nonce: number;
+
/**
* The BIP-32 path to the account.
*/
diff --git a/yarn.lock b/yarn.lock
index 6fed90c..1aa2258 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14277,6 +14277,13 @@ __metadata:
languageName: node
linkType: hard
+"int64-buffer@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "int64-buffer@npm:1.0.1"
+ checksum: 9962be285f4a0d6bd8f6fba3cffcfd80b15848af370bd9ec6cb2d9c8a8adf83b230cdf66b694f87c992c1a33724385b28ba7cac61602a7fcf9b9c8691015c7e2
+ languageName: node
+ linkType: hard
+
"internal-slot@npm:^1.0.3":
version: 1.0.3
resolution: "internal-slot@npm:1.0.3"
@@ -20888,6 +20895,7 @@ __metadata:
eslint-plugin-jsdoc: ^39.2.9
eslint-plugin-node: ^11.1.0
eslint-plugin-prettier: ^4.2.1
+ int64-buffer: ^1.0.1
jest: ^29.5.0
prettier: ^2.2.1
prettier-plugin-packagejson: ^2.2.11