Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ This project adheres to
- Removed `@internal` JSDoc tags from public APIs so they appear in generated
documentation. https://github.com/o1-labs/o1js/pull/2881

### Fixed

- Reject Base64 inputs with padding characters before the end of the string.

### Removed

- Removed unused `Cairo*` gates. https://github.com/o1-labs/o1js/pull/2752
Expand Down
36 changes: 26 additions & 10 deletions src/lib/provable/bytes.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { provableFromClass } from './types/provable-derivers.js';
import type { ProvablePureExtended } from './types/struct.js';
import { assert } from './gadgets/common.js';
import { chunk, chunkString } from '../util/arrays.js';
import { Provable } from './provable.js';
import { UInt8 } from './int.js';
import { randomBytes } from '../../bindings/crypto/random.js';
import { Field } from './field.js';
import { chunk, chunkString } from '../util/arrays.js';
import { Bool } from './bool.js';
import { Field } from './field.js';
import { assert } from './gadgets/common.js';
import { UInt8 } from './int.js';
import { Provable } from './provable.js';
import { provableFromClass } from './types/provable-derivers.js';
import type { ProvablePureExtended } from './types/struct.js';

// external API
export { Bytes, FlexibleBytes };
Expand Down Expand Up @@ -132,15 +132,15 @@ class Bytes {
* @param byteLength The length of the output decoded bytes.
* @returns Decoded bytes as {@link Bytes}.
*
* @warning
* Ensure the input Base64 string does not contain '=' characters in the middle,
* as it can cause unexpected decoding results.
* @throws If the input length is not a multiple of 4 or if padding characters
* appear before the end of the input.
*/
base64Decode(byteLength: number): Bytes {
const encodedB64Bytes = this.bytes;

const charLength = encodedB64Bytes.length;
assert(charLength % 4 === 0, 'Input base64 byte length should be a multiple of 4!');
assertBase64Padding(encodedB64Bytes);

let decodedB64Bytes: UInt8[] = new Array(byteLength).fill(UInt8.from(0));

Expand Down Expand Up @@ -224,6 +224,22 @@ function createBytes(size: number): typeof Bytes {
};
}

function assertBase64Padding(encodedB64Bytes: UInt8[]) {
const paddingError = 'Base64 padding is only allowed at the end of the input';
let previousIsPadding = new Bool(false);

for (let i = 0; i < encodedB64Bytes.length; i++) {
const isPadding = encodedB64Bytes[i].value.equals(61);

if (i < encodedB64Bytes.length - 2) {
isPadding.assertFalse(paddingError);
} else {
previousIsPadding.and(isPadding.not()).assertFalse(paddingError);
previousIsPadding = previousIsPadding.or(isPadding);
}
}
}

/**
* Decodes a Base64 character to its original value.
* Adapted from the algorithm described in: http://0x80.pl/notesen/2016-01-17-sse-base64-decoding.html#vector-lookup-base
Expand Down
11 changes: 9 additions & 2 deletions src/lib/provable/test/base64.unit-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Bytes } from '../wrapped-classes.js';
import { describe, test } from 'node:test';
import { expect } from 'expect';
import { describe, test } from 'node:test';
import { Bytes } from '../wrapped-classes.js';

function calculateB64DecodedBytesLength(base64String: string): number {
// Calculate the length of the base64-encoded string
Expand Down Expand Up @@ -72,6 +72,13 @@ describe('Base64 Decode Tests', () => {
'Please provide Base64-encoded bytes containing only alphanumeric characters and +/=';
expect(() => testBase64Decode(input)).toThrowError(errorMessage);
});

test('should reject padding before the end of the input', async () => {
const input = 'Zm=9';
expect(() => Bytes.fromString(input).base64Decode(2).toBytes()).toThrowError(
'Base64 padding is only allowed at the end of the input'
);
});
});

describe('Base64 Encode Tests', () => {
Expand Down