-
Notifications
You must be signed in to change notification settings - Fork 7
WIP: SecpPrivKeySegment impl for FFM #482
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| /* | ||
| * Copyright 2023-2026 secp256k1-jdk Developers. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.bitcoinj.secp.ffm; | ||
|
|
||
| import org.bitcoinj.secp.SecpPrivKey; | ||
| import org.bitcoinj.secp.internal.ByteArray; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.NotSerializableException; | ||
| import java.io.ObjectInputStream; | ||
| import java.io.ObjectOutputStream; | ||
| import java.io.Serial; | ||
| import java.lang.foreign.Arena; | ||
| import java.lang.foreign.MemorySegment; | ||
| import java.math.BigInteger; | ||
| import java.util.Arrays; | ||
|
|
||
| import static java.lang.foreign.ValueLayout.JAVA_BYTE; | ||
|
|
||
| /// Native implementation of SecpPrivKey | ||
| class SecpPrivKeySegment implements SecpPrivKey { | ||
| private static final int KEY_LENGTH = 32; | ||
|
|
||
| private volatile boolean destroyed = false; | ||
| private final MemorySegment segment; | ||
|
|
||
| SecpPrivKeySegment(MemorySegment privKeySeg) { | ||
| var segment = Arena.ofAuto().allocate(KEY_LENGTH); | ||
| MemorySegment.copy(privKeySeg, 0, segment, 0, KEY_LENGTH); | ||
| this.segment = segment; | ||
| } | ||
|
|
||
| SecpPrivKeySegment(byte[] privKeyBytes) { | ||
| this(MemorySegment.ofArray(privKeyBytes)); | ||
| } | ||
|
|
||
| @Override | ||
| public byte[] getEncoded() { | ||
| if (destroyed) throwKeyDestroyed(); | ||
| return segment.toArray(JAVA_BYTE); | ||
| } | ||
|
|
||
| @Override | ||
| public BigInteger getS() { | ||
| if (destroyed) throwKeyDestroyed(); | ||
| byte[] bytes = getEncoded(); | ||
| try { | ||
| return ByteArray.toInteger(bytes); | ||
| } finally { | ||
| Arrays.fill(bytes, (byte) 0); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void destroy() { | ||
| // TODO: Make sure the zeroing is not optimized out by the compiler or JIT | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is the C equivalent. I believe this may work: private static final VarHandle BYTE_VH = ValueLayout.JAVA_BYTE.varHandle();
public void destroy() {
if (destroyed) return;
for (long i = 0, n = segment.byteSize(); i < n; i++) {
BYTE_VH.setVolatile(segment, i, (byte) 0);
}
destroyed = true;
}This basically allows for volatile writes to off-heap memory, which shouldn't be optimized away.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's in interesting idea. I think we should find out whether the JDK/FFM can optimize out a call to |
||
| if (!destroyed) { | ||
| segment.fill((byte) 0); | ||
| destroyed = true; | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isDestroyed() { | ||
| return destroyed; | ||
| } | ||
|
|
||
| MemorySegment segment() { | ||
| if (destroyed) throwKeyDestroyed(); | ||
| return segment.asReadOnly(); | ||
| } | ||
|
|
||
| private void throwKeyDestroyed() { | ||
| throw new IllegalStateException("Private Key has been destroyed"); | ||
| } | ||
|
|
||
| @Serial | ||
| private void writeObject(ObjectOutputStream out) throws IOException { | ||
| throw new NotSerializableException("Serialization of private keys is prohibited."); | ||
| } | ||
|
|
||
| @Serial | ||
| private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { | ||
| throw new NotSerializableException("Deserialization of private keys is prohibited."); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| /* | ||
| * Copyright 2023-2026 secp256k1-jdk Developers. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.bitcoinj.secp.ffm; | ||
|
|
||
| import org.bitcoinj.secp.Secp256k1; | ||
| import org.bitcoinj.secp.SecpPrivKey; | ||
| import org.bitcoinj.secp.SecpPubKey; | ||
| import org.bitcoinj.secp.internal.SecpScalarImpl; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import java.math.BigInteger; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| /// Unit tests of [SecpPrivKeySegment] | ||
| public class SecpPrivKeySegmentTest { | ||
|
|
||
| @Test | ||
| void constructOne() { | ||
| var oneInt = BigInteger.ONE; | ||
| var oneBytes = SecpScalarImpl.integerTo32Bytes(oneInt); | ||
|
|
||
| var onePrivKey = new SecpPrivKeySegment(oneBytes); | ||
| assertEquals(oneInt, onePrivKey.getS()); | ||
|
|
||
| var onePrivKeyClone = new SecpPrivKeySegment(onePrivKey.segment()); | ||
| assertEquals(oneInt, onePrivKeyClone.getS()); | ||
| } | ||
|
|
||
| @Test | ||
| void constructAndDestroy() { | ||
| try (Secp256k1Foreign secp = new Secp256k1Foreign()) { | ||
| SecpPrivKey privKey = secp.ecPrivKeyCreate(); | ||
| assertFalse(privKey.isDestroyed()); | ||
| privKey.destroy(); | ||
| assertTrue(privKey.isDestroyed()); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void multiplyTestOne() { | ||
| try (Secp256k1Foreign secp = new Secp256k1Foreign()) { | ||
| SecpPrivKey onePrivKey = new SecpPrivKeySegment(SecpScalarImpl.integerTo32Bytes(BigInteger.ONE)); | ||
| SecpPubKey pubKey = secp.ecPubKeyCreate(onePrivKey); | ||
| SecpPubKey check = secp.ecPubKeyTweakMul(Secp256k1.G, onePrivKey.getS()); | ||
| assertEquals(pubKey.x(), check.x()); | ||
| assertEquals(pubKey.y(), check.y()); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void multiplyTest() { | ||
| try (Secp256k1Foreign secp = new Secp256k1Foreign()) { | ||
| SecpPrivKey privKey = secp.ecPrivKeyCreate(); | ||
| SecpPubKey pubKey = secp.ecPubKeyCreate(privKey); | ||
| SecpPubKey check = secp.ecPubKeyTweakMul(Secp256k1.G, privKey.getS()); | ||
| assertEquals(pubKey.x(), check.x()); | ||
| assertEquals(pubKey.y(), check.y()); | ||
| } | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comment can be a little more clear. Perhaps it should be stated that generally non-temporary segments are from the Secp*Segment objects and temporary segments are all others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the method should be named
zeroIfWritable? It's a low-level helper method and it doesn't really know what is temporary or not -- it just zerosMemorySegmentsthat are writable. We could then document how it used elsewhere.