mirror of
https://github.com/vacp2p/nim-libp2p-experimental.git
synced 2025-01-13 03:44:26 +00:00
Signed envelopes and routing records (#656)
This commit is contained in:
parent
73168b6eae
commit
0dfac6fce7
@ -1005,7 +1005,7 @@ proc `$`*(pat: MaPattern): string =
|
|||||||
proc write*(pb: var ProtoBuffer, field: int, value: MultiAddress) {.inline.} =
|
proc write*(pb: var ProtoBuffer, field: int, value: MultiAddress) {.inline.} =
|
||||||
write(pb, field, value.data.buffer)
|
write(pb, field, value.data.buffer)
|
||||||
|
|
||||||
proc getField*(pb: var ProtoBuffer, field: int,
|
proc getField*(pb: ProtoBuffer, field: int,
|
||||||
value: var MultiAddress): ProtoResult[bool] {.
|
value: var MultiAddress): ProtoResult[bool] {.
|
||||||
inline.} =
|
inline.} =
|
||||||
var buffer: seq[byte]
|
var buffer: seq[byte]
|
||||||
|
@ -201,6 +201,7 @@ const MultiCodecList = [
|
|||||||
("p2p-webrtc-direct", 0x0114), # not in multicodec list
|
("p2p-webrtc-direct", 0x0114), # not in multicodec list
|
||||||
("onion", 0x01BC),
|
("onion", 0x01BC),
|
||||||
("p2p-circuit", 0x0122),
|
("p2p-circuit", 0x0122),
|
||||||
|
("libp2p-peer-record", 0x0301),
|
||||||
("dns", 0x35),
|
("dns", 0x35),
|
||||||
("dns4", 0x36),
|
("dns4", 0x36),
|
||||||
("dns6", 0x37),
|
("dns6", 0x37),
|
||||||
|
125
libp2p/routing_record.nim
Normal file
125
libp2p/routing_record.nim
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
## Nim-Libp2p
|
||||||
|
## Copyright (c) 2021 Status Research & Development GmbH
|
||||||
|
## Licensed under either of
|
||||||
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
## at your option.
|
||||||
|
## This file may not be copied, modified, or distributed except according to
|
||||||
|
## those terms.
|
||||||
|
|
||||||
|
## This module implements Routing Records.
|
||||||
|
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
|
import std/[sequtils, times]
|
||||||
|
import pkg/stew/[results, byteutils]
|
||||||
|
import
|
||||||
|
multiaddress,
|
||||||
|
multicodec,
|
||||||
|
peerid,
|
||||||
|
protobuf/minprotobuf,
|
||||||
|
signed_envelope
|
||||||
|
|
||||||
|
export peerid, multiaddress, signed_envelope
|
||||||
|
|
||||||
|
## Constants relating to signed peer records
|
||||||
|
const
|
||||||
|
EnvelopeDomain = multiCodec("libp2p-peer-record") # envelope domain as per RFC0002
|
||||||
|
EnvelopePayloadType= @[(byte) 0x03, (byte) 0x01] # payload_type for routing records as spec'ed in RFC0003
|
||||||
|
|
||||||
|
type
|
||||||
|
AddressInfo* = object
|
||||||
|
address*: MultiAddress
|
||||||
|
|
||||||
|
PeerRecord* = object
|
||||||
|
peerId*: PeerId
|
||||||
|
seqNo*: uint64
|
||||||
|
addresses*: seq[AddressInfo]
|
||||||
|
|
||||||
|
proc decode*(
|
||||||
|
T: typedesc[PeerRecord],
|
||||||
|
buffer: seq[byte]): Result[PeerRecord, ProtoError] =
|
||||||
|
|
||||||
|
let pb = initProtoBuffer(buffer)
|
||||||
|
var record = PeerRecord()
|
||||||
|
|
||||||
|
? pb.getRequiredField(1, record.peerId)
|
||||||
|
? pb.getRequiredField(2, record.seqNo)
|
||||||
|
|
||||||
|
var addressInfos: seq[seq[byte]]
|
||||||
|
let pb3 = ? pb.getRepeatedField(3, addressInfos)
|
||||||
|
|
||||||
|
if pb3:
|
||||||
|
for address in addressInfos:
|
||||||
|
var addressInfo = AddressInfo()
|
||||||
|
let subProto = initProtoBuffer(address)
|
||||||
|
if ? subProto.getField(1, addressInfo.address) == false:
|
||||||
|
return err(ProtoError.RequiredFieldMissing)
|
||||||
|
|
||||||
|
record.addresses &= addressInfo
|
||||||
|
|
||||||
|
ok(record)
|
||||||
|
|
||||||
|
proc encode*(record: PeerRecord): seq[byte] =
|
||||||
|
var pb = initProtoBuffer()
|
||||||
|
|
||||||
|
pb.write(1, record.peerId)
|
||||||
|
pb.write(2, record.seqNo)
|
||||||
|
|
||||||
|
for address in record.addresses:
|
||||||
|
var addrPb = initProtoBuffer()
|
||||||
|
addrPb.write(1, address.address)
|
||||||
|
pb.write(3, addrPb)
|
||||||
|
|
||||||
|
pb.finish()
|
||||||
|
pb.buffer
|
||||||
|
|
||||||
|
proc init*(T: typedesc[PeerRecord],
|
||||||
|
peerId: PeerId,
|
||||||
|
seqNo: uint64,
|
||||||
|
addresses: seq[MultiAddress]): T =
|
||||||
|
|
||||||
|
PeerRecord(
|
||||||
|
peerId: peerId,
|
||||||
|
seqNo: seqNo,
|
||||||
|
addresses: addresses.mapIt(AddressInfo(address: it))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## Functions related to signed peer records
|
||||||
|
|
||||||
|
proc init*(T: typedesc[Envelope],
|
||||||
|
privateKey: PrivateKey,
|
||||||
|
peerRecord: PeerRecord): Result[Envelope, CryptoError] =
|
||||||
|
|
||||||
|
## Init a signed envelope wrapping a peer record
|
||||||
|
|
||||||
|
let envelope = ? Envelope.init(privateKey,
|
||||||
|
EnvelopePayloadType,
|
||||||
|
peerRecord.encode(),
|
||||||
|
$EnvelopeDomain)
|
||||||
|
|
||||||
|
ok(envelope)
|
||||||
|
|
||||||
|
proc init*(T: typedesc[Envelope],
|
||||||
|
peerId: PeerId,
|
||||||
|
addresses: seq[MultiAddress],
|
||||||
|
privateKey: PrivateKey): Result[Envelope, CryptoError] =
|
||||||
|
## Creates a signed peer record for this peer:
|
||||||
|
## a peer routing record according to https://github.com/libp2p/specs/blob/500a7906dd7dd8f64e0af38de010ef7551fd61b6/RFC/0003-routing-records.md
|
||||||
|
## in a signed envelope according to https://github.com/libp2p/specs/blob/500a7906dd7dd8f64e0af38de010ef7551fd61b6/RFC/0002-signed-envelopes.md
|
||||||
|
|
||||||
|
# First create a peer record from the peer info
|
||||||
|
let peerRecord = PeerRecord.init(peerId,
|
||||||
|
getTime().toUnix().uint64, # This currently follows the recommended implementation, using unix epoch as seq no.
|
||||||
|
addresses)
|
||||||
|
|
||||||
|
let envelope = ? Envelope.init(privateKey,
|
||||||
|
peerRecord)
|
||||||
|
|
||||||
|
ok(envelope)
|
||||||
|
|
||||||
|
proc getSignedPeerRecord*(pb: ProtoBuffer, field: int,
|
||||||
|
value: var Envelope): ProtoResult[bool] {.
|
||||||
|
inline.} =
|
||||||
|
getField(pb, field, value, $EnvelopeDomain)
|
118
libp2p/signed_envelope.nim
Normal file
118
libp2p/signed_envelope.nim
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
## Nim-Libp2p
|
||||||
|
## Copyright (c) 2021 Status Research & Development GmbH
|
||||||
|
## Licensed under either of
|
||||||
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
## at your option.
|
||||||
|
## This file may not be copied, modified, or distributed except according to
|
||||||
|
## those terms.
|
||||||
|
|
||||||
|
## This module implements Signed Envelope.
|
||||||
|
|
||||||
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
|
import pkg/stew/[results, byteutils]
|
||||||
|
import multicodec,
|
||||||
|
crypto/crypto,
|
||||||
|
protobuf/minprotobuf,
|
||||||
|
vbuffer
|
||||||
|
|
||||||
|
export crypto
|
||||||
|
|
||||||
|
type
|
||||||
|
EnvelopeError* = enum
|
||||||
|
EnvelopeInvalidProtobuf,
|
||||||
|
EnvelopeFieldMissing,
|
||||||
|
EnvelopeInvalidSignature
|
||||||
|
|
||||||
|
Envelope* = object
|
||||||
|
publicKey*: PublicKey
|
||||||
|
domain*: string
|
||||||
|
payloadType*: seq[byte]
|
||||||
|
payload: seq[byte]
|
||||||
|
signature*: Signature
|
||||||
|
|
||||||
|
proc mapProtobufError(e: ProtoError): EnvelopeError =
|
||||||
|
case e:
|
||||||
|
of RequiredFieldMissing:
|
||||||
|
EnvelopeFieldMissing
|
||||||
|
else:
|
||||||
|
EnvelopeInvalidProtobuf
|
||||||
|
|
||||||
|
proc getSignatureBuffer(e: Envelope): seq[byte] =
|
||||||
|
var buffer = initVBuffer()
|
||||||
|
|
||||||
|
let domainBytes = e.domain.toBytes()
|
||||||
|
buffer.writeSeq(domainBytes)
|
||||||
|
buffer.writeSeq(e.payloadType)
|
||||||
|
buffer.writeSeq(e.payload)
|
||||||
|
|
||||||
|
buffer.buffer
|
||||||
|
|
||||||
|
proc decode*(T: typedesc[Envelope],
|
||||||
|
buf: seq[byte],
|
||||||
|
domain: string): Result[Envelope, EnvelopeError] =
|
||||||
|
|
||||||
|
let pb = initProtoBuffer(buf)
|
||||||
|
var envelope = Envelope()
|
||||||
|
|
||||||
|
envelope.domain = domain
|
||||||
|
? pb.getRequiredField(1, envelope.publicKey).mapErr(mapProtobufError)
|
||||||
|
discard ? pb.getField(2, envelope.payloadType).mapErr(mapProtobufError)
|
||||||
|
? pb.getRequiredField(3, envelope.payload).mapErr(mapProtobufError)
|
||||||
|
? pb.getRequiredField(5, envelope.signature).mapErr(mapProtobufError)
|
||||||
|
|
||||||
|
if envelope.signature.verify(envelope.getSignatureBuffer(), envelope.publicKey) == false:
|
||||||
|
err(EnvelopeInvalidSignature)
|
||||||
|
else:
|
||||||
|
ok(envelope)
|
||||||
|
|
||||||
|
proc init*(T: typedesc[Envelope],
|
||||||
|
privateKey: PrivateKey,
|
||||||
|
payloadType: seq[byte],
|
||||||
|
payload: seq[byte],
|
||||||
|
domain: string): Result[Envelope, CryptoError] =
|
||||||
|
var envelope = Envelope(
|
||||||
|
publicKey: ? privateKey.getPublicKey(),
|
||||||
|
domain: domain,
|
||||||
|
payloadType: payloadType,
|
||||||
|
payload: payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
envelope.signature = ? privateKey.sign(envelope.getSignatureBuffer())
|
||||||
|
|
||||||
|
ok(envelope)
|
||||||
|
|
||||||
|
proc encode*(env: Envelope): Result[seq[byte], CryptoError] =
|
||||||
|
var pb = initProtoBuffer()
|
||||||
|
|
||||||
|
try:
|
||||||
|
pb.write(1, env.publicKey)
|
||||||
|
pb.write(2, env.payloadType)
|
||||||
|
pb.write(3, env.payload)
|
||||||
|
pb.write(5, env.signature)
|
||||||
|
except ResultError[CryptoError] as exc:
|
||||||
|
return err(exc.error)
|
||||||
|
|
||||||
|
pb.finish()
|
||||||
|
ok(pb.buffer)
|
||||||
|
|
||||||
|
proc payload*(env: Envelope): seq[byte] =
|
||||||
|
# Payload is readonly
|
||||||
|
env.payload
|
||||||
|
|
||||||
|
proc getField*(pb: ProtoBuffer, field: int,
|
||||||
|
value: var Envelope,
|
||||||
|
domain: string): ProtoResult[bool] {.
|
||||||
|
inline.} =
|
||||||
|
var buffer: seq[byte]
|
||||||
|
let res = ? pb.getField(field, buffer)
|
||||||
|
if not(res):
|
||||||
|
ok(false)
|
||||||
|
else:
|
||||||
|
let env = Envelope.decode(buffer, domain)
|
||||||
|
if env.isOk():
|
||||||
|
value = env.get()
|
||||||
|
ok(true)
|
||||||
|
else:
|
||||||
|
err(ProtoError.IncorrectBlob)
|
@ -15,7 +15,9 @@ import testmultibase,
|
|||||||
testmultihash,
|
testmultihash,
|
||||||
testmultiaddress,
|
testmultiaddress,
|
||||||
testcid,
|
testcid,
|
||||||
testpeerid
|
testpeerid,
|
||||||
|
testsigned_envelope,
|
||||||
|
testrouting_record
|
||||||
|
|
||||||
import testtcptransport,
|
import testtcptransport,
|
||||||
testnameresolve,
|
testnameresolve,
|
||||||
|
38
tests/testrouting_record.nim
Normal file
38
tests/testrouting_record.nim
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import unittest2
|
||||||
|
import stew/byteutils
|
||||||
|
import ../libp2p/[routing_record, crypto/crypto]
|
||||||
|
|
||||||
|
suite "Routing record":
|
||||||
|
test "Encode -> decode test":
|
||||||
|
let
|
||||||
|
rng = newRng()
|
||||||
|
privKey = PrivateKey.random(rng[]).tryGet()
|
||||||
|
peerId = PeerId.init(privKey).tryGet()
|
||||||
|
multiAddresses = @[MultiAddress.init("/ip4/0.0.0.0/tcp/24").tryGet(), MultiAddress.init("/ip4/0.0.0.0/tcp/25").tryGet()]
|
||||||
|
routingRecord = PeerRecord.init(peerId, 42, multiAddresses)
|
||||||
|
|
||||||
|
buffer = routingRecord.encode()
|
||||||
|
|
||||||
|
parsedRR = PeerRecord.decode(buffer).tryGet()
|
||||||
|
|
||||||
|
check:
|
||||||
|
parsedRR.peerId == peerId
|
||||||
|
parsedRR.seqNo == 42
|
||||||
|
parsedRR.addresses.len == 2
|
||||||
|
parsedRR.addresses[0].address == multiAddresses[0]
|
||||||
|
parsedRR.addresses[1].address == multiAddresses[1]
|
||||||
|
|
||||||
|
test "Interop decode":
|
||||||
|
let
|
||||||
|
# from https://github.com/libp2p/go-libp2p-core/blob/b18a4c9c5629870bde2cd85ab3b87a507600d411/peer/record_test.go#L33
|
||||||
|
# (with only 2 addresses)
|
||||||
|
inputData = "0a2600240801122011bba3ed1721948cefb4e50b0a0bb5cad8a6b52dc7b1a40f4f6652105c91e2c4109bf59d8dd99d8ddb161a0a0a0804010203040600001a0a0a080401020304060001".hexToSeqByte()
|
||||||
|
decodedRecord = PeerRecord.decode(inputData).tryGet()
|
||||||
|
|
||||||
|
check:
|
||||||
|
$decodedRecord.peerId == "12D3KooWB1b3qZxWJanuhtseF3DmPggHCtG36KZ9ixkqHtdKH9fh"
|
||||||
|
decodedRecord.seqNo == uint64 1636553709551319707
|
||||||
|
decodedRecord.addresses.len == 2
|
||||||
|
$decodedRecord.addresses[0].address == "/ip4/1.2.3.4/tcp/0"
|
||||||
|
$decodedRecord.addresses[1].address == "/ip4/1.2.3.4/tcp/1"
|
||||||
|
|
30
tests/testsigned_envelope.nim
Normal file
30
tests/testsigned_envelope.nim
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import unittest2
|
||||||
|
import stew/byteutils
|
||||||
|
import ../libp2p/[signed_envelope]
|
||||||
|
|
||||||
|
suite "Signed envelope":
|
||||||
|
test "Encode -> decode test":
|
||||||
|
let
|
||||||
|
rng = newRng()
|
||||||
|
privKey = PrivateKey.random(rng[]).tryGet()
|
||||||
|
envelope = Envelope.init(privKey, @[byte 12, 0], "payload".toBytes(), "domain").tryGet()
|
||||||
|
buffer = envelope.encode().tryGet()
|
||||||
|
decodedEnvelope = Envelope.decode(buffer, "domain").tryGet()
|
||||||
|
wrongDomain = Envelope.decode(buffer, "wdomain")
|
||||||
|
|
||||||
|
check:
|
||||||
|
decodedEnvelope == envelope
|
||||||
|
wrongDomain.error == EnvelopeInvalidSignature
|
||||||
|
|
||||||
|
test "Interop decode test":
|
||||||
|
# from https://github.com/libp2p/go-libp2p-core/blob/b18a4c9c5629870bde2cd85ab3b87a507600d411/record/envelope_test.go#L68
|
||||||
|
let inputData = "0a24080112206f1581709bb7b1ef030d210db18e3b0ba1c776fba65d8cdaad05415142d189f812102f6c69627032702f74657374646174611a0c68656c6c6f20776f726c64212a401178673b51dfa842aad17e465e25d646ad16628916b964c3fb10c711fee87872bdd4e4646f58c277cdff09704913d8be1aec6322de8d3d0bb852120374aece08".hexToSeqByte()
|
||||||
|
let decodedEnvelope = Envelope.decode(inputData, "libp2p-testing").tryGet()
|
||||||
|
check:
|
||||||
|
decodedEnvelope.payloadType == "/libp2p/testdata".toBytes()
|
||||||
|
decodedEnvelope.payload == "hello world!".toBytes()
|
||||||
|
|
||||||
|
test "Signature validation":
|
||||||
|
# same as above, but payload altered
|
||||||
|
let inputData = "0a24080112206f1581709bb7b1ef030d210db18e3b0ba1c776fba65d8cdaad05415142d189f812102f6c69627032702f74657374646174611a0c00006c6c6f20776f726c64212a401178673b51dfa842aad17e465e25d646ad16628916b964c3fb10c711fee87872bdd4e4646f58c277cdff09704913d8be1aec6322de8d3d0bb852120374aece08".hexToSeqByte()
|
||||||
|
check Envelope.decode(inputData, "libp2p-testing").error == EnvelopeInvalidSignature
|
Loading…
x
Reference in New Issue
Block a user