From 574ea259aab9928e20e15fae0f45353eeec5b0ac Mon Sep 17 00:00:00 2001 From: George Date: Wed, 13 Nov 2024 11:01:56 -0800 Subject: [PATCH 1/6] Add a way to return object from authorizeEntry callback --- src/auth.js | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/auth.js b/src/auth.js index 0c206e0c..571ee550 100644 --- a/src/auth.js +++ b/src/auth.js @@ -17,11 +17,13 @@ import { nativeToScVal } from './scval'; * whose hash you should sign, so that you can inspect the entire structure * if necessary (rather than blindly signing a hash) * - * @returns {Promise} the signature of the raw payload (which is - * the sha256 hash of the preimage bytes, so `hash(preimage.toXDR())`) signed - * by the key corresponding to the public key in the entry you pass to - * {@link authorizeEntry} (decipherable from its - * `credentials().address().address()`) + * @returns { + * Promise | + * Promise<{signature: Uint8Array, signer: string} + * } the signature of the raw payload (which is the sha256 hash of the preimage + * bytes, so `hash(preimage.toXDR())`) signed by the key corresponding to the + * public key in the entry you pass to {@link authorizeEntry} (decipherable + * from its `credentials().address().address()`) */ /** @@ -43,10 +45,16 @@ import { nativeToScVal } from './scval'; * * @param {xdr.SorobanAuthorizationEntry} entry an unsigned authorization entr * @param {Keypair | SigningCallback} signer either a {@link Keypair} instance - * or a function which takes a payload (a - * {@link xdr.HashIdPreimageSorobanAuthorization} instance) input and returns - * the signature of the hash of the raw payload bytes (where the signing key - * should correspond to the address in the `entry`) + * or a function which takes a {@link xdr.HashIdPreimageSorobanAuthorization} + * input payload and returns EITHER + * + * (a) an object containing a `signature` in bytes and a `signer` in bytes + * (the public key that created this signature), or + * (b) just the signature of the hash of the raw payload bytes (where the + * signing key should correspond to the address in the `entry`). + * + * The latter option (b) is JUST for backwards compatibility and will be + * removed in the future. * @param {number} validUntilLedgerSeq the (exclusive) future ledger sequence * number until which this authorization entry should be valid (if * `currentLedgerSeq==validUntil`, this is expired)) @@ -137,8 +145,15 @@ export async function authorizeEntry( let signature; let publicKey; if (typeof signer === 'function') { - signature = Buffer.from(await signer(preimage)); - publicKey = Address.fromScAddress(addrAuth.address()).toString(); + const sigResult = await signer(preimage); + if (typeof sigResult === 'object') { + signature = Buffer.from(sigResult.signature); + publicKey = sigResult.signer; + } else { + // if using the deprecated form, assume it's for the entry + signature = Buffer.from(sigResult); + publicKey = Address.fromScAddress(addrAuth.address()).toString(); + } } else { signature = Buffer.from(signer.sign(payload)); publicKey = signer.publicKey(); From 7ea11443f8d35f1132be6de423bab7e08e17d128 Mon Sep 17 00:00:00 2001 From: George Date: Wed, 13 Nov 2024 11:07:08 -0800 Subject: [PATCH 2/6] Fix type, add changelog --- CHANGELOG.md | 11 +++++++++++ types/index.d.ts | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a59a05a..f6b3f4de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## Unreleased +### Added +* Within `authorizeEntry`, the `SigningCallback` callback function should now return an object containing both the signature and the identity of the signer. In multi-signature situations, it isn't necessarily the case that the address within the authorization entry is the one that actually signs that entry. Thus, the callback now takes the following form, where the original `Promise` option is preserved for backwards compatibility and should be considered deprecated ([]()): +```typescript +export type SigningCallback = ( + preimage: xdr.HashIdPreimage +) => Promise< + BufferLike | + { signature: BufferLike, signer: BufferLike } +>; +``` + ## [`v13.0.0`](https://github.com/stellar/js-stellar-base/compare/v12.1.1...v13.0.0) diff --git a/types/index.d.ts b/types/index.d.ts index 99ca9f5e..456747ca 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1214,9 +1214,13 @@ export class SorobanDataBuilder { build(): xdr.SorobanTransactionData; } +type BufferLike = Buffer | Uint8Array | ArrayBuffer; export type SigningCallback = ( preimage: xdr.HashIdPreimage -) => Promise; +) => Promise< + BufferLike | + { signature: BufferLike, signer: BufferLike } +>; export function authorizeInvocation( signer: Keypair | SigningCallback, From bfd5aa9f4d878ee509f7551940c3638495c0522d Mon Sep 17 00:00:00 2001 From: George Date: Wed, 13 Nov 2024 11:10:32 -0800 Subject: [PATCH 3/6] Rename to publicKey per issue --- CHANGELOG.md | 2 +- src/auth.js | 11 ++++++----- types/index.d.ts | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b3f4de..02d8751b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ export type SigningCallback = ( preimage: xdr.HashIdPreimage ) => Promise< BufferLike | - { signature: BufferLike, signer: BufferLike } + { signature: BufferLike, publicKey: BufferLike } >; ``` diff --git a/src/auth.js b/src/auth.js index 571ee550..0f214cc3 100644 --- a/src/auth.js +++ b/src/auth.js @@ -19,11 +19,12 @@ import { nativeToScVal } from './scval'; * * @returns { * Promise | - * Promise<{signature: Uint8Array, signer: string} + * Promise<{signature: Uint8Array, publicKey: string} * } the signature of the raw payload (which is the sha256 hash of the preimage - * bytes, so `hash(preimage.toXDR())`) signed by the key corresponding to the - * public key in the entry you pass to {@link authorizeEntry} (decipherable - * from its `credentials().address().address()`) + * bytes, so `hash(preimage.toXDR())`) either naked, implying it is signed + * by the key corresponding to the public key in the entry you pass to + * {@link authorizeEntry} (decipherable from its + * `credentials().address().address()`), or alongside an explicit `publicKey`. */ /** @@ -148,7 +149,7 @@ export async function authorizeEntry( const sigResult = await signer(preimage); if (typeof sigResult === 'object') { signature = Buffer.from(sigResult.signature); - publicKey = sigResult.signer; + publicKey = sigResult.publicKey; } else { // if using the deprecated form, assume it's for the entry signature = Buffer.from(sigResult); diff --git a/types/index.d.ts b/types/index.d.ts index 456747ca..2ae0fcd3 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1219,7 +1219,7 @@ export type SigningCallback = ( preimage: xdr.HashIdPreimage ) => Promise< BufferLike | - { signature: BufferLike, signer: BufferLike } + { signature: BufferLike, publicKey: BufferLike } >; export function authorizeInvocation( From 616004054a88cae13a94d6a607f8ba8ec724f7b1 Mon Sep 17 00:00:00 2001 From: George Date: Wed, 13 Nov 2024 11:16:58 -0800 Subject: [PATCH 4/6] Fix detection, add test --- src/auth.js | 15 ++++++++------- test/unit/auth_test.js | 15 +++++++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/auth.js b/src/auth.js index 0f214cc3..5934a67d 100644 --- a/src/auth.js +++ b/src/auth.js @@ -49,10 +49,11 @@ import { nativeToScVal } from './scval'; * or a function which takes a {@link xdr.HashIdPreimageSorobanAuthorization} * input payload and returns EITHER * - * (a) an object containing a `signature` in bytes and a `signer` in bytes - * (the public key that created this signature), or - * (b) just the signature of the hash of the raw payload bytes (where the - * signing key should correspond to the address in the `entry`). + * (a) an object containing a `signature` of the hash of the raw payload bytes + * as a Buffer-like and a `publicKey` string representing who just + * created this signature, or + * (b) just the naked signature of the hash of the raw payload bytes (where + * the signing key is implied to be the address in the `entry`). * * The latter option (b) is JUST for backwards compatibility and will be * removed in the future. @@ -67,8 +68,8 @@ import { nativeToScVal } from './scval'; * {@link Operation.invokeHostFunction} * * @note If using the `SigningCallback` variation, the signer is assumed to be - * the entry's credential address. If you need a different key to sign the - * entry, you will need to use different method (e.g., fork this code). + * the entry's credential address unless you use the variant that returns + * the object. * * @see authorizeInvocation * @example @@ -147,7 +148,7 @@ export async function authorizeEntry( let publicKey; if (typeof signer === 'function') { const sigResult = await signer(preimage); - if (typeof sigResult === 'object') { + if (sigResult?.signature) { signature = Buffer.from(sigResult.signature); publicKey = sigResult.publicKey; } else { diff --git a/test/unit/auth_test.js b/test/unit/auth_test.js index b7903681..7446ea28 100644 --- a/test/unit/auth_test.js +++ b/test/unit/auth_test.js @@ -35,7 +35,16 @@ describe('building authorization entries', function () { [ [kp, 'Keypair'], - [(preimage) => kp.sign(StellarBase.hash(preimage.toXDR())), 'callback'] + [(preimage) => kp.sign(StellarBase.hash(preimage.toXDR())), 'callback'], + [ + (preimage) => { + return { + signature: kp.sign(StellarBase.hash(preimage.toXDR())), + publicKey: kp.publicKey() + }; + }, + 'callback w/ obj' + ] ].forEach(([signer, methodName]) => { it(`signs the entry correctly (${methodName})`, function (done) { StellarBase.authorizeEntry(authEntry, signer, 10) @@ -87,9 +96,7 @@ describe('building authorization entries', function () { it('can build from scratch', function (done) { StellarBase.authorizeInvocation(kp, 10, authEntry.rootInvocation()) - .then((signedEntry) => { - done(); - }) + .then((signedEntry) => done()) .catch((err) => done(err)); }); }); From 1d839ac4e1615ff842107a8319e4fe6a65b0a032 Mon Sep 17 00:00:00 2001 From: George Date: Thu, 14 Nov 2024 09:17:20 -0800 Subject: [PATCH 5/6] Fix typing since public key is string Co-authored-by: Jun Luo <4catcode@gmail.com> --- types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index 2ae0fcd3..941cb856 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1219,7 +1219,7 @@ export type SigningCallback = ( preimage: xdr.HashIdPreimage ) => Promise< BufferLike | - { signature: BufferLike, publicKey: BufferLike } + { signature: BufferLike, publicKey: string } >; export function authorizeInvocation( From babfb13321e43f909d5ddbd834137ab99808fdd3 Mon Sep 17 00:00:00 2001 From: George Date: Thu, 14 Nov 2024 09:17:33 -0800 Subject: [PATCH 6/6] Fix typing since public key is string Co-authored-by: Jun Luo <4catcode@gmail.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d8751b..9397c1bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ export type SigningCallback = ( preimage: xdr.HashIdPreimage ) => Promise< BufferLike | - { signature: BufferLike, publicKey: BufferLike } + { signature: BufferLike, publicKey: string } >; ```