Skip to content
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

Bandersnatch library #33

Merged
merged 19 commits into from
Jul 23, 2024
Merged
31 changes: 27 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,41 @@ jobs:
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Cache static lib files
- name: Cache Cargo
uses: actions/cache@v4
with:
path: .lib
key: ${{ runner.os }}-libs-${{ steps.blst-commit-hash.outputs.commit-hash }}
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
Utils/Sources/bandersnatch/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Cache blst static lib
uses: actions/cache@v4
with:
path: .lib/libblst.a
key: ${{ runner.os }}-libs-blst-${{ steps.blst-commit-hash.outputs.commit-hash }}
restore-keys: |
${{ runner.os }}-libs-blst
- name: Cache bandersnatch_vrfs static lib
uses: actions/cache@v4
with:
path: .lib/libbandersnatch_vrfs.a
key: ${{ runner.os }}-libs-libbandersnatch-${{ hashFiles('Utils/Sources/bandersnatch/**') }}
restore-keys: |
${{ runner.os }}-libs-
${{ runner.os }}-libs-libbandersnatch
- name: Setup Swift
uses: SwiftyLab/setup-swift@latest
with:
swift-version: '6.0'
development: true
- name: Setup Rust
uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- name: Check rust format
run: cargo +nightly fmt --all --manifest-path Utils/Sources/bandersnatch/Cargo.toml -- --check
- name: Build
run: make build
- name: Test
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ DerivedData/
.netrc
.vscode/settings.json

# rust build
target

# static lib files
.lib/
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ default: build
githooks: .git/hooks/pre-commit

.PHONY: deps
deps: .lib/libblst.a
deps: .lib/libblst.a .lib/libbandersnatch_vrfs.a

.lib/libblst.a:
./scripts/blst.sh

.lib/libbandersnatch_vrfs.a: $(wildcard Utils/Sources/bandersnatch/src/*)
./scripts/bandersnatch.sh

.PHONY: test
test: githooks deps
./scripts/run.sh test
Expand All @@ -32,7 +35,7 @@ resolve: githooks
.PHONY: clean
clean:
./scripts/run.sh package clean
rm Utils/Sources/blst/lib/libblst.a
rm -f .lib/*.a

.PHONY: lint
lint: githooks
Expand Down
5 changes: 5 additions & 0 deletions Utils/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ let package = Package(
.product(name: "Blake2", package: "Blake2.swift"),
.product(name: "Crypto", package: "swift-crypto"),
"blst",
"bandersnatch_vrfs",
],
linkerSettings: [
.unsafeFlags(["-L../.lib"]),
Expand All @@ -40,6 +41,10 @@ let package = Package(
name: "blst",
path: "Sources"
),
.systemLibrary(
name: "bandersnatch_vrfs",
path: "Sources"
),
.testTarget(
name: "UtilsTests",
dependencies: [
Expand Down
180 changes: 180 additions & 0 deletions Utils/Sources/Utils/Bandersnatch.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import bandersnatch_vrfs
import Foundation

public enum BandersnatchError: Error {
case createSecretFailed
case deserializePubKeyFailed
case generatePubKeyFailed
case createProverFailed
case createVerifierFailed
case ringVRFSignFailed
case ietfVRFSignFailed
case verifyRingVrfFailed
case verifyIetfVrfFailed
}

extension Data {
init(cSecret: CSecret) {
let tuple = cSecret._0
// a short way to convert (UInt8, UInt8, ...) to [UInt8, UInt8, ...]
let array: [UInt8] = Swift.withUnsafeBytes(of: tuple) {
Array($0.bindMemory(to: UInt8.self))
}
self.init(array)
}

init(cPublic: CPublic) {
let tuple = cPublic._0
let array: [UInt8] = Swift.withUnsafeBytes(of: tuple) {
Array($0.bindMemory(to: UInt8.self))
}
self.init(array)
}
}

extension CPublic {
private static func deserialize(data: Data) throws -> CPublic {
let cPublicPtr = public_deserialize_compressed([UInt8](data), UInt(data.count))
guard let cPublicPtr else {
throw BandersnatchError.deserializePubKeyFailed
}
return CPublic(_0: cPublicPtr.pointee._0)
}

init(data: Data) throws {
self = try CPublic.deserialize(data: data)
}

init(data32: Data32) throws {
self = try CPublic.deserialize(data: data32.data)
}
}

public struct Bandersnatch {
public let secret: Data96
public let publicKey: Data32

public init(seed: Data) throws {
let seedBytes = [UInt8](seed)
let secretPtr = secret_new_from_seed(seedBytes, UInt(seed.count))
guard let secretPtr else {
throw BandersnatchError.createSecretFailed
}

secret = Data96(Data(cSecret: secretPtr.pointee))!

let publicPtr = secret_get_public(secretPtr)
guard let publicPtr else {
throw BandersnatchError.generatePubKeyFailed
}

publicKey = Data32(Data(cPublic: publicPtr.pointee))!
}
}

public class Prover {
private var prover: OpaquePointer

/// init with a set of bandersnatch public keys and provider index
public init(ring: [Data32], proverIdx: UInt) throws {
var success = false
let cPublicArr = try ring.map { try CPublic(data32: $0) }
prover = prover_new(cPublicArr, UInt(ring.count), proverIdx, &success)
if !success {
throw BandersnatchError.createProverFailed
}
}

deinit {
prover_free(prover)
}

/// Anonymous VRF signature.
///
/// Used for tickets submission.
public func ringVRFSign(vrfInputData: Data, auxData: Data) throws -> Data784 {
var output = [UInt8](repeating: 0, count: 784)
let success = prover_ring_vrf_sign(
&output, prover, [UInt8](vrfInputData), UInt(vrfInputData.count), [UInt8](auxData),
UInt(auxData.count)
)
if !success {
throw BandersnatchError.ringVRFSignFailed
}
return Data784(Data(output))!
}

/// Non-Anonymous VRF signature.
///
/// Used for ticket claiming during block production.
/// Not used with Safrole test vectors.
public func ietfVRFSign(vrfInputData: Data, auxData: Data) throws -> Data96 {
var output = [UInt8](repeating: 0, count: 96)
let success = prover_ietf_vrf_sign(
&output, prover, [UInt8](vrfInputData), UInt(vrfInputData.count), [UInt8](auxData),
UInt(auxData.count)
)
if !success {
throw BandersnatchError.ietfVRFSignFailed
}
return Data96(Data(output))!
}
}

public class Verifier {
private var verifier: OpaquePointer

public init(ring: [Data32]) throws {
var success = false
let cPublicArr = try ring.map { try CPublic(data32: $0) }
verifier = verifier_new(cPublicArr, UInt(ring.count), &success)
if !success {
throw BandersnatchError.createVerifierFailed
}
}

deinit {
verifier_free(verifier)
}

/// Anonymous VRF signature verification.
///
/// Used for tickets verification.
///
/// On success returns the VRF output hash.
public func ringVRFVerify(vrfInputData: Data, auxData: Data, signature: Data) -> Result<
Data32, BandersnatchError
> {
var output = [UInt8](repeating: 0, count: 32)
let success = verifier_ring_vrf_verify(
&output, verifier, [UInt8](vrfInputData), UInt(vrfInputData.count), [UInt8](auxData),
UInt(auxData.count), [UInt8](signature), UInt(signature.count)
)
if !success {
return .failure(.verifyRingVrfFailed)
}
return .success(Data32(Data(output))!)
}

/// Non-Anonymous VRF signature verification.
///
/// Used for ticket claim verification during block import.
/// Not used with Safrole test vectors.
///
/// On success returns the VRF output hash.
public func ietfVRFVerify(
vrfInputData: Data, auxData: Data, signature: Data, signerKeyIndex: UInt
)
-> Result<Data32, BandersnatchError>
{
var output = [UInt8](repeating: 0, count: 32)
let success = verifier_ietf_vrf_verify(
&output, verifier, [UInt8](vrfInputData), UInt(vrfInputData.count), [UInt8](auxData),
UInt(auxData.count), [UInt8](signature), UInt(signature.count), signerKeyIndex
)
if !success {
return .failure(.verifyIetfVrfFailed)
}
return .success(Data32(Data(output))!)
}
}
Loading
Loading