diff --git a/src/main/java/org/peergos/EmbeddedIpfs.java b/src/main/java/org/peergos/EmbeddedIpfs.java index 234f5661..c9af94e5 100644 --- a/src/main/java/org/peergos/EmbeddedIpfs.java +++ b/src/main/java/org/peergos/EmbeddedIpfs.java @@ -105,7 +105,12 @@ public CompletableFuture publishValue(PrivKey priv, byte[] value, long seq Multihash pub = Multihash.deserialize(PeerId.fromPubKey(priv.publicKey()).getBytes()); LocalDateTime expiry = LocalDateTime.now().plusHours(hoursTtl); long ttlNanos = hoursTtl * 3600_000_000_000L; - return dht.publishValue(priv, pub, value, sequence, expiry, ttlNanos, node); + byte[] signedRecord = IPNS.createSignedRecord(value, expiry, sequence, ttlNanos, priv); + return dht.publishValue(pub, signedRecord, node); + } + + public CompletableFuture publishPresignedRecord(Multihash pub, byte[] presignedRecord) { + return dht.publishValue(pub, presignedRecord, node); } public CompletableFuture resolveValue(PubKey pub) { diff --git a/src/main/java/org/peergos/protocol/dht/Kademlia.java b/src/main/java/org/peergos/protocol/dht/Kademlia.java index 3e821d65..93c7d591 100644 --- a/src/main/java/org/peergos/protocol/dht/Kademlia.java +++ b/src/main/java/org/peergos/protocol/dht/Kademlia.java @@ -313,31 +313,23 @@ public CompletableFuture publishIpnsValue(PrivKey priv, LocalDateTime expiry = LocalDateTime.now().plusHours(hours); long ttlNanos = hours * 3600_000_000_000L; byte[] publishValue = ("/ipfs/" + value).getBytes(); - return publishValue(priv, publisher, publishValue, sequence, expiry, ttlNanos, us); + byte[] signedRecord = IPNS.createSignedRecord(publishValue, expiry, sequence, ttlNanos, priv); + return publishValue(publisher, signedRecord, us); } - private boolean putValue(PrivKey priv, - Multihash publisher, - byte[] publishValue, - long sequence, - LocalDateTime expiry, - long ttlNanos, + private boolean putValue(Multihash publisher, + byte[] signedRecord, PeerAddresses peer, Host us) { try { return dialPeer(peer, us).join() - .putValue(publishValue, expiry, sequence, - ttlNanos, publisher, priv).join(); + .putValue(publisher, signedRecord).join(); } catch (Exception e) {} return false; } - public CompletableFuture publishValue(PrivKey priv, - Multihash publisher, - byte[] publishValue, - long sequence, - LocalDateTime expiry, - long ttlNanos, + public CompletableFuture publishValue(Multihash publisher, + byte[] signedRecord, Host us) { Set publishes = Collections.synchronizedSet(new HashSet<>()); int minPublishes = 20; @@ -370,7 +362,7 @@ public CompletableFuture publishValue(PrivKey priv, } } ioExec.submit(() -> { - if (putValue(priv, publisher, publishValue, sequence, expiry, ttlNanos, r.addresses, us)) + if (putValue(publisher, signedRecord, r.addresses, us)) publishes.add(r.addresses.peerId); }); return true; @@ -396,7 +388,7 @@ public CompletableFuture publishValue(PrivKey priv, toQuery.remove(r); queried.add(r.addresses.peerId); return ioExec.submit(() -> { - if (putValue(priv, publisher, publishValue, sequence, expiry, ttlNanos, r.addresses, us)) + if (putValue(publisher, signedRecord, r.addresses, us)) publishes.add(r.addresses.peerId); }); }) diff --git a/src/main/java/org/peergos/protocol/dht/KademliaController.java b/src/main/java/org/peergos/protocol/dht/KademliaController.java index e5f6aa99..98e4fbb0 100644 --- a/src/main/java/org/peergos/protocol/dht/KademliaController.java +++ b/src/main/java/org/peergos/protocol/dht/KademliaController.java @@ -47,31 +47,6 @@ default CompletableFuture getProviders(Multihash block) { .thenApply(Providers::fromProtobuf); } - default CompletableFuture putValue(byte[] value, LocalDateTime expiry, long sequence, - long ttlNanos, Multihash peerId, PrivKey ourKey) { - byte[] cborEntryData = IPNS.createCborDataForIpnsEntry(value, expiry, - Ipns.IpnsEntry.ValidityType.EOL_VALUE, sequence, ttlNanos); - String expiryString = IPNS.formatExpiry(expiry); - byte[] signature = ourKey.sign(IPNS.createSigV2Data(cborEntryData)); - PubKey pubKey = ourKey.publicKey(); - byte[] pubKeyProtobuf = Crypto.PublicKey.newBuilder() - .setType(pubKey.getKeyType()) - .setData(ByteString.copyFrom(pubKey.raw())) - .build() - .toByteArray(); - byte[] ipnsEntry = Ipns.IpnsEntry.newBuilder() - .setSequence(sequence) - .setTtl(ttlNanos) - .setValue(ByteString.copyFrom(value)) - .setValidityType(Ipns.IpnsEntry.ValidityType.EOL) - .setValidity(ByteString.copyFrom(expiryString.getBytes())) - .setData(ByteString.copyFrom(cborEntryData)) - .setSignatureV2(ByteString.copyFrom(signature)) - .setPubKey(ByteString.copyFrom(pubKeyProtobuf)) // not needed with Ed25519 - .build().toByteArray(); - return putValue(peerId, ipnsEntry); - } - default CompletableFuture putValue(Multihash peerId, byte[] value) { byte[] ipnsRecordKey = IPNS.getKey(peerId); Dht.Message outgoing = Dht.Message.newBuilder() diff --git a/src/main/java/org/peergos/protocol/ipns/IPNS.java b/src/main/java/org/peergos/protocol/ipns/IPNS.java index 044a6a56..8210c1eb 100644 --- a/src/main/java/org/peergos/protocol/ipns/IPNS.java +++ b/src/main/java/org/peergos/protocol/ipns/IPNS.java @@ -34,6 +34,35 @@ public static byte[] getKey(Multihash peerId) { return bout.toByteArray(); } + public static byte[] createSignedRecord(byte[] value, + LocalDateTime expiry, + long sequence, + long ttlNanos, + PrivKey ourKey) { + byte[] cborEntryData = IPNS.createCborDataForIpnsEntry(value, expiry, + Ipns.IpnsEntry.ValidityType.EOL_VALUE, sequence, ttlNanos); + String expiryString = IPNS.formatExpiry(expiry); + byte[] signature = ourKey.sign(IPNS.createSigV2Data(cborEntryData)); + PubKey pubKey = ourKey.publicKey(); + Ipns.IpnsEntry.Builder entryBuilder = Ipns.IpnsEntry.newBuilder() + .setSequence(sequence) + .setTtl(ttlNanos) + .setValue(ByteString.copyFrom(value)) + .setValidityType(Ipns.IpnsEntry.ValidityType.EOL) + .setValidity(ByteString.copyFrom(expiryString.getBytes())) + .setData(ByteString.copyFrom(cborEntryData)) + .setSignatureV2(ByteString.copyFrom(signature)); + if (ourKey.getKeyType() != Crypto.KeyType.Ed25519) { + byte[] pubKeyProtobuf = Crypto.PublicKey.newBuilder() + .setType(pubKey.getKeyType()) + .setData(ByteString.copyFrom(pubKey.raw())) + .build() + .toByteArray(); + entryBuilder = entryBuilder.setPubKey(ByteString.copyFrom(pubKeyProtobuf)); // not needed with Ed25519 + } + return entryBuilder.build().toByteArray(); + } + public static Cid getCidFromKey(ByteString key) { if (! key.startsWith(ByteString.copyFrom("/ipns/".getBytes(StandardCharsets.UTF_8)))) throw new IllegalStateException("Unknown IPNS key space: " + key); diff --git a/src/test/java/org/peergos/EmbeddedIpfsTest.java b/src/test/java/org/peergos/EmbeddedIpfsTest.java index ac398b81..d8cb836b 100644 --- a/src/test/java/org/peergos/EmbeddedIpfsTest.java +++ b/src/test/java/org/peergos/EmbeddedIpfsTest.java @@ -3,6 +3,7 @@ import identify.pb.*; import io.ipfs.cid.*; import io.ipfs.multiaddr.*; +import io.ipfs.multihash.Multihash; import io.libp2p.core.*; import io.libp2p.core.crypto.*; import io.libp2p.core.multiformats.*; @@ -12,7 +13,9 @@ import org.peergos.blockstore.*; import org.peergos.config.*; import org.peergos.protocol.dht.*; +import org.peergos.protocol.ipns.*; +import java.time.*; import java.util.*; import java.util.concurrent.*; import java.util.stream.*; @@ -54,6 +57,28 @@ public void publishValue() throws Exception { node1.stop(); } + @Test + public void publishPresignedValue() throws Exception { + EmbeddedIpfs node1 = build(BootstrapTest.BOOTSTRAP_NODES, List.of(new MultiAddress("/ip4/127.0.0.1/tcp/" + TestPorts.getPort()))); + node1.start(); + + PrivKey publisher = Ed25519Kt.generateEd25519KeyPair().getFirst(); + byte[] value = "This is a test".getBytes(); + io.ipfs.multihash.Multihash pub = Multihash.deserialize(PeerId.fromPubKey(publisher.publicKey()).getBytes()); + long hoursTtl = 24*2; + LocalDateTime expiry = LocalDateTime.now().plusHours(hoursTtl); + long ttlNanos = hoursTtl * 3600_000_000_000L; + byte[] signedRecord = IPNS.createSignedRecord(value, expiry, 1, ttlNanos, publisher); + node1.publishPresignedRecord(pub, signedRecord).join(); + node1.publishPresignedRecord(pub, signedRecord).join(); + node1.publishPresignedRecord(pub, signedRecord).join(); + + byte[] res = node1.resolveValue(publisher.publicKey()).join(); + Assert.assertTrue(Arrays.equals(res, value)); + + node1.stop(); + } + @Test public void wildcardListenerAddressesGetExpanded() { int node1Port = TestPorts.getPort(); diff --git a/src/test/java/org/peergos/IpnsTest.java b/src/test/java/org/peergos/IpnsTest.java index 8ea57bff..72d4d3fa 100644 --- a/src/test/java/org/peergos/IpnsTest.java +++ b/src/test/java/org/peergos/IpnsTest.java @@ -2,7 +2,6 @@ import io.ipfs.api.*; import io.ipfs.cid.*; -import io.ipfs.multiaddr.*; import io.ipfs.multihash.Multihash; import io.libp2p.core.*; import io.libp2p.core.multiformats.*; @@ -48,8 +47,9 @@ public void publishIPNSRecordToKubo() throws IOException { for (int i = 0; i < 10; i++) { try { + byte[] value = IPNS.createSignedRecord(pathToPublish.getBytes(), expiry, sequence, ttl, node1.getPrivKey()); success = dht.dial(node1, address2).getController().join() - .putValue(pathToPublish.getBytes(), expiry, sequence, ttl, node1Id, node1.getPrivKey()) + .putValue(node1Id, value) .orTimeout(2, TimeUnit.SECONDS).join(); break; } catch (Exception timeout) {