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

Multisig: Connect request and SignMultisigTransaction request #458

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8cb07e3
Add test for signing a multisig transaction
sisou Sep 26, 2022
a520a0a
Add `signPartially` method to Key and use in test
sisou Sep 26, 2022
ee6a2ba
MonkeyPatch MerkleTree to verify multisig sender address
sisou Sep 26, 2022
4b00fdf
[WIP] Add SignMultisigTransaction request
sisou Sep 26, 2022
b80eabd
Add demo for multisig signing
sisou Sep 27, 2022
7402611
Add multisig config badge to AddressInfo component
sisou Sep 27, 2022
07da998
Add user and account name section
sisou Sep 29, 2022
a609892
Update title wording
sisou May 22, 2023
f605504
Demo of generating random RSA keys in a sandboxed iframe
sisou Oct 7, 2022
b10662f
Use node-forge in sandboxed iframe to generate deterministic RSA key
sisou Oct 7, 2022
8de308a
Use ArrayBuffers to exchange key material
sisou Oct 10, 2022
35425a4
Extend 32-byte entropy to 1024-byte seed with PBKDF2
sisou Oct 10, 2022
4259030
Refactor Key config into an object and extend with rsaKeyPair option
sisou Oct 11, 2022
ff780c7
Add RSA key computation to Key class
sisou Oct 11, 2022
f90da33
Implement encrypted secret parsing and decryption
sisou Oct 11, 2022
9fb4580
Add Rust code compiled to WASM for secret aggregation
sisou Oct 11, 2022
e424038
Update multisig badge UI, show identicon for legacy accounts
sisou Oct 12, 2022
59aa873
Connect request
sisou Oct 22, 2023
293763a
Extract LoginFileAccountIcon into a component
sisou Oct 18, 2022
36d898e
Refactor multisig request type structure
sisou Oct 19, 2022
7223628
Add comment about permission additions
sisou Oct 21, 2022
0593220
Narrow encryptionKey algorithm type
sisou Oct 21, 2022
63bf825
Add sandboxed iframe files to dist during build
sisou Oct 24, 2022
edb4c2d
Extract inline-script from RSAKeysIframe
sisou Oct 24, 2022
b1ea4a9
Add explainer tooltip to connect UI
sisou May 22, 2023
79a8eae
Clean-up, improve error handling
sisou Oct 22, 2023
d5959d5
Fix types for KeyguardCommand
sisou Dec 1, 2022
7841d8b
Allow RSA key generation to use dynamic parameters
sisou Dec 5, 2022
820d23f
Apply fixes from the Typescript update (PR #467)
sisou Sep 24, 2024
9157108
Allow signing multisig transactions from a vesting contract
sisou Sep 28, 2024
fbbcf06
Update SignMultisigTransaction flow to Albatross PoS SDK
sisou Jan 15, 2025
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
Prev Previous commit
Next Next commit
Implement encrypted secret parsing and decryption
  • Loading branch information
sisou committed Jan 15, 2025
commit f90da3358218366689480fa31a617bc6a5f40d65
7 changes: 6 additions & 1 deletion client/src/PublicRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,12 @@ export type MultisigInfo = {
publicKeys: Uint8Array[],
numberOfSigners: number,
signerPublicKeys: Uint8Array[],
secret: Uint8Array,
secret: {
aggregatedSecret: Uint8Array,
} | {
encryptedSecrets: Uint8Array[],
bScalar: Uint8Array,
},
aggregatedCommitment: Uint8Array,
userName?: string,
};
Expand Down
4 changes: 3 additions & 1 deletion demos/SignMultisigTransaction.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@
publicKeys: publicKeys.map(key => key.serialize()),
numberOfSigners,
signerPublicKeys: publicKeys.slice(0, numberOfSigners).map(key => key.serialize()),
secret: Nimiq.PrivateKey.generate().serialize(), // Using random data
secret: {
aggregatedSecret: Nimiq.PrivateKey.generate().serialize(), // Using random data
},
aggregatedCommitment: Nimiq.PrivateKey.generate().serialize(), // Using random data
userName,
};
Expand Down
9 changes: 9 additions & 0 deletions src/lib/multisig/MultisigUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,13 @@ class MultisigUtils { // eslint-disable-line no-unused-vars
const merkleRoot = Nimiq.MerkleTree.computeRoot(multiSigKeys);
return Nimiq.Address.fromHash(merkleRoot);
}

/**
* @param {Uint8Array[]} secrets
* @param {Uint8Array} bScalar
* @returns {Promise<Nimiq.RandomSecret>}
*/
static async aggregateSecrets(secrets, bScalar) { // eslint-disable-line no-unused-vars
throw new Error('NOT IMPLEMENTED!');
}
}
19 changes: 18 additions & 1 deletion src/request/sign-multisig-transaction/SignMultisigTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
/* global I18n */
/* global LoginFileConfig */
/* global IqonHash */
/* global MultisigUtils */

/**
* @callback SignMultisigTransaction.resolve
Expand Down Expand Up @@ -234,11 +235,27 @@ class SignMultisigTransaction {
return;
}

/** @type {Nimiq.RandomSecret} */
let aggregatedSecret;
if ('aggregatedSecret' in request.multisig.secret) {
aggregatedSecret = request.multisig.secret.aggregatedSecret;
} else {
// If we only have encrypted secrets, decrypt them and aggregate them with the bScalar
const rsaKey = await key.getRsaPrivateKey();
const secrets = await Promise.all(request.multisig.secret.encryptedSecrets.map(
async encrypted => new Uint8Array(
await window.crypto.subtle.decrypt({ name: 'RSA-OAEP' }, rsaKey, encrypted),
),
));

aggregatedSecret = await MultisigUtils.aggregateSecrets(secrets, request.multisig.secret.bScalar);
}

const signature = key.signPartially(
request.keyPath,
request.transaction.serializeContent(),
request.multisig.signerPublicKeys,
request.multisig.secret,
aggregatedSecret,
request.multisig.aggregatedCommitment,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/* global SignMultisigTransaction */
/* global MultisigUtils */
/* global Errors */
/* global CONFIG */

/** @extends {TopLevelApi<KeyguardRequest.SignMultisigTransactionRequest>} */
class SignMultisigTransactionApi extends TopLevelApi {
Expand Down Expand Up @@ -114,12 +115,48 @@ class SignMultisigTransactionApi extends TopLevelApi {
throw new Errors.InvalidRequestError(`Invalid signer public keys: ${error.message}`);
}

/** @type {Nimiq.RandomSecret} */
/** @type {MultisigConfig['secret']} */
let secret;
try {
secret = new Nimiq.RandomSecret(object.secret);
} catch (error) {
throw new Errors.InvalidRequestError(`Invalid secret: ${error.message}`);
if (typeof object.secret !== 'object') {
throw new Errors.InvalidRequestError('Invalid secret: must be an object');
}
if ('aggregatedSecret' in object.secret) {
try {
secret = {
aggregatedSecret: new Nimiq.RandomSecret(object.secret.aggregatedSecret),
};
} catch (error) {
throw new Errors.InvalidRequestError(`Invalid secret: ${error.message}`);
}
} else if ('encryptedSecrets' in object.secret && 'bScalar' in object.secret) {
// Not checking fixed length here, to stay flexible for future increases of the number of commitments
if (!Array.isArray(object.secret.encryptedSecrets) || object.secret.encryptedSecrets.length < 2) {
throw new Errors.InvalidRequestError(
'Invalid secret.encryptedSecrets: must be an array with at least 2 elements',
);
}
const rsaCipherLength = CONFIG.RSA_KEY_BITS / 8;
if (object.secret.encryptedSecrets.some(
/**
* @param {unknown} array
* @returns {boolean}
*/
array => !(array instanceof Uint8Array)
|| array.length !== rsaCipherLength,
)) {
throw new Errors.InvalidRequestError(
`Invalid secret.encryptedSecrets: must be an array of Uint8Array(${rsaCipherLength})`,
);
}
if (!(object.secret.bScalar instanceof Uint8Array) || object.secret.bScalar.length !== 32) {
throw new Errors.InvalidRequestError('Invalid secret.bScalar: must be an Uint8Array(32)');
}
secret = {
encryptedSecrets: object.secret.encryptedSecrets,
bScalar: object.secret.bScalar,
};
} else {
throw new Errors.InvalidRequestError('Invalid secret format');
}

/** @type {Nimiq.RandomSecret} */
Expand Down
7 changes: 6 additions & 1 deletion types/Keyguard.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ type MultisigConfig = {
publicKeys: Nimiq.PublicKey[]
numberOfSigners: number
signerPublicKeys: Nimiq.PublicKey[]
secret: Nimiq.RandomSecret
secret: {
aggregatedSecret: Nimiq.RandomSecret
} | {
encryptedSecrets: Uint8Array[]
bScalar: Uint8Array
}
aggregatedCommitment: Nimiq.Commitment
userName?: string
}
Expand Down