Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .changeset/tall-bobcats-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@solana/codecs-numbers': patch
'@solana/codecs-strings': patch
'@solana/codecs-core': patch
'@solana/compat': patch
'@solana/keys': patch
---

Any `SharedArrayBuffer` that gets passed to a crypto operation like `signBytes` or `verifySignature` will now be copied as non-shared. Crypto operations like `sign` and `verify` reject `SharedArrayBuffers` otherwise
2 changes: 1 addition & 1 deletion packages/codecs-core/src/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type Offset = number;
*/
type BaseEncoder<TFrom> = {
/** Encode the provided value and return the encoded bytes directly. */
readonly encode: (value: TFrom) => ReadonlyUint8Array;
readonly encode: (value: TFrom) => ReadonlyUint8Array<ArrayBuffer>;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about this narrowing, @lorisleiva. I presume that encoders should never return a SharedArrayBuffer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. I can't think of a scenario where you'd want the returned bytes to be shared — and if that was the case, you'd probably want to be super explicit about it and wrap the encoder into something else.

/**
* Writes the encoded value into the provided byte array at the given offset.
* Returns the offset of the next byte after the encoded value.
Expand Down
3 changes: 2 additions & 1 deletion packages/codecs-core/src/readonly-uint8array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
* bytes[0] = 42; // Type error: Cannot assign to '0' because it is a read-only property.
* ```
*/
export interface ReadonlyUint8Array extends Omit<Uint8Array, TypedArrayMutableProperties> {
export interface ReadonlyUint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
extends Omit<Uint8Array<TArrayBuffer>, TypedArrayMutableProperties> {
readonly [n: number]: number;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/codecs-strings/src/base64.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
combineCodec,
createDecoder,
createEncoder,
toArrayBuffer,
transformDecoder,
transformEncoder,
VariableSizeCodec,
Expand Down Expand Up @@ -112,7 +113,7 @@ export const getBase64Decoder = (): VariableSizeDecoder<string> => {

if (__NODEJS__) {
return createDecoder({
read: (bytes, offset = 0) => [Buffer.from(bytes, offset).toString('base64'), bytes.length],
read: (bytes, offset = 0) => [Buffer.from(toArrayBuffer(bytes), offset).toString('base64'), bytes.length],
});
}

Expand Down
8 changes: 2 additions & 6 deletions packages/compat/src/__tests__/instruction-test.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import '@solana/test-matchers/toBeFrozenObject';

import { ImplicitArrayBuffer } from 'node:buffer';

import { address } from '@solana/addresses';
import { AccountRole, Instruction } from '@solana/instructions';
import { PublicKey, TransactionInstruction } from '@solana/web3.js';

import { fromLegacyPublicKey } from '../address';
import { fromLegacyTransactionInstruction } from '../instruction';

function toLegacyByteArrayAppropriateForPlatform<TArrayBuffer extends ArrayBufferLike>(
data: ImplicitArrayBuffer<TArrayBuffer>,
) {
function toLegacyByteArrayAppropriateForPlatform<TArrayBuffer extends ArrayBuffer>(data: Uint8Array<TArrayBuffer>) {
if (__NODEJS__) {
return Buffer.from(data);
} else {
return new Uint8Array(data as TArrayBuffer) as Buffer<TArrayBuffer>;
return new Uint8Array(data) as Buffer<TArrayBuffer>;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/keys/src/private-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SOLANA_ERROR__KEYS__INVALID_PRIVATE_KEY_BYTE_LENGTH, SolanaError } from

import { ED25519_ALGORITHM_IDENTIFIER } from './algorithm';

function addPkcs8Header(bytes: ReadonlyUint8Array): ReadonlyUint8Array {
function addPkcs8Header(bytes: ReadonlyUint8Array): ReadonlyUint8Array<ArrayBuffer> {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we're able to express that it's readonly, and non-shared (because we just created it inline).

// prettier-ignore
return new Uint8Array([
/**
Expand Down
6 changes: 3 additions & 3 deletions packages/keys/src/signatures.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assertSigningCapabilityIsAvailable, assertVerificationCapabilityIsAvailable } from '@solana/assertions';
import { Encoder, ReadonlyUint8Array } from '@solana/codecs-core';
import { Encoder, ReadonlyUint8Array, toArrayBuffer } from '@solana/codecs-core';
import { getBase58Encoder } from '@solana/codecs-strings';
import {
SOLANA_ERROR__KEYS__INVALID_SIGNATURE_BYTE_LENGTH,
Expand Down Expand Up @@ -185,7 +185,7 @@ export function isSignatureBytes(putativeSignatureBytes: ReadonlyUint8Array): pu
*/
export async function signBytes(key: CryptoKey, data: ReadonlyUint8Array): Promise<SignatureBytes> {
assertSigningCapabilityIsAvailable();
const signedData = await crypto.subtle.sign(ED25519_ALGORITHM_IDENTIFIER, key, data);
const signedData = await crypto.subtle.sign(ED25519_ALGORITHM_IDENTIFIER, key, toArrayBuffer(data));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allows people to pass SharedArrayBuffers in here for signing, without triggering a runtime error.

return new Uint8Array(signedData) as SignatureBytes;
}

Expand Down Expand Up @@ -249,5 +249,5 @@ export async function verifySignature(
data: ReadonlyUint8Array,
): Promise<boolean> {
assertVerificationCapabilityIsAvailable();
return await crypto.subtle.verify(ED25519_ALGORITHM_IDENTIFIER, key, signature, data);
return await crypto.subtle.verify(ED25519_ALGORITHM_IDENTIFIER, key, toArrayBuffer(signature), toArrayBuffer(data));
}