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