diff --git a/Dockerfile b/Dockerfile index 2d55dc6..25d494f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY *.go ./ +COPY crypt ./crypt COPY wrap ./wrap COPY cmd ./cmd RUN CGO_ENABLED=0 go build -trimpath -buildvcs=false -ldflags=-buildid= -o /modelwrap ./cmd/modelwrap diff --git a/cmd/modelwrap/launcher.go b/cmd/modelwrap/launcher.go index 2e5b346..f80024b 100644 --- a/cmd/modelwrap/launcher.go +++ b/cmd/modelwrap/launcher.go @@ -49,10 +49,6 @@ func launch(opts cliOptions) int { // of the same CLI inside the packer image. func dockerRunArgs(opts cliOptions) ([]string, error) { args := []string{"run", "--rm"} - if opts.Encrypt { - // EMWP packing needs loop device and device-mapper access. - args = append(args, "--privileged") - } hostDir := func(path, fallback string) (string, error) { if path == "" { diff --git a/cmd/modelwrap/launcher_test.go b/cmd/modelwrap/launcher_test.go index e3a6ef1..be97594 100644 --- a/cmd/modelwrap/launcher_test.go +++ b/cmd/modelwrap/launcher_test.go @@ -38,7 +38,7 @@ func TestDockerRunArgs(t *testing.T) { } want := []string{ - "run", "--rm", "--privileged", + "run", "--rm", "-v", filepath.Join(dir, "output") + ":/output", "-v", filepath.Join(dir, "cache") + ":/cache", "-v", filepath.Join(dir, "weights") + ":/model:ro", @@ -54,6 +54,13 @@ func TestDockerRunArgs(t *testing.T) { t.Fatalf("dockerRunArgs mismatch:\n got %q\nwant %q", got, want) } + // EMWP packing is fully userspace and must not request privilege. + for _, arg := range got { + if arg == "--privileged" { + t.Fatal("EMWP packing must not be privileged") + } + } + // Secret values must never appear in the docker command line. for _, arg := range got { if arg == "secret" { diff --git a/crypt/crypt.go b/crypt/crypt.go new file mode 100644 index 0000000..31ba10c --- /dev/null +++ b/crypt/crypt.go @@ -0,0 +1,160 @@ +// Package crypt implements the EMWP dm-crypt encryption (aes-xts-plain64, +// 512-bit key, 4096-byte sectors) in pure Go. +// +// The output is byte-identical to `cryptsetup open --type plain` with the +// parameters the packer uses, so the kernel dm-crypt consumer decrypts +// modelwrap artifacts unchanged. Producing the ciphertext in userspace lets +// the packer encrypt EMWP artifacts without cryptsetup, loop devices, +// device-mapper, or a privileged container, and without writing the volume +// key to disk. +// +// Only the IV-per-sector convention is dm-crypt specific; everything else +// (the two-key split, the tweak derivation, the little-endian sector +// encoding) is standard XTS as implemented by golang.org/x/crypto/xts. That +// one convention is pinned by the dm-crypt golden-vector test. +package crypt + +import ( + "crypto/aes" + "fmt" + "io" + + "golang.org/x/crypto/xts" + + "github.com/tinfoilsh/modelwrap" +) + +// SectorSize is the dm-crypt data-unit size (cryptsetup --sector-size). Each +// SectorSize-byte block is encrypted as one XTS data unit: the tweak is +// derived once from the sector's IV and chained by the GF(2^128) multiply +// across the block's 16-byte AES blocks. +const SectorSize = modelwrap.EMWPSectorSize + +// ivSectorRatio converts a SectorSize-byte sector index into the 512-byte +// sector number that plain64 uses for the IV. dm-crypt keeps 512-byte IV +// numbering unless cryptsetup is given --iv-large-sectors, which the packer +// does not use, so each 4096-byte sector advances the IV by 8. This is the +// single dm-crypt specific convention; if TestDmcryptGolden ever fails, this +// constant (8 vs 1) is the first thing to check. +const ivSectorRatio = SectorSize / 512 + +// streamChunkBytes is the streaming buffer size: 4 MiB, sector-aligned, +// matching the old copyToDevice buffer so large artifacts never load fully +// into memory. +const streamChunkBytes = 1024 * SectorSize + +// The packer and consumer always open dm-crypt with skip 0, so the volume's +// first sector is IV unit 0; there is no non-zero-skip path to support. + +func newCipher(volumeKey []byte) (*xts.Cipher, error) { + if len(volumeKey) != modelwrap.EMWPKeyBytes { + return nil, fmt.Errorf("volume key is %d bytes, want %d", len(volumeKey), modelwrap.EMWPKeyBytes) + } + return xts.NewCipher(aes.NewCipher, volumeKey) +} + +// transform encrypts or decrypts a sector-aligned buffer in place. baseUnit +// is the 0-based sector index of buf[0] within the volume. +func transform(c *xts.Cipher, buf []byte, baseUnit uint64, decrypt bool) { + for off := 0; off < len(buf); off += SectorSize { + iv := (baseUnit + uint64(off/SectorSize)) * ivSectorRatio + s := buf[off : off+SectorSize] + if decrypt { + c.Decrypt(s, s, iv) + } else { + c.Encrypt(s, s, iv) + } + } +} + +// Encrypt encrypts a whole sector-aligned plaintext with the raw 64-byte +// dm-crypt volume key (already derived via modelwrap.DeriveKey). It is a +// convenience for small buffers and tests; the packer uses EncryptStream. +func Encrypt(volumeKey, plaintext []byte) ([]byte, error) { + return inMemory(volumeKey, plaintext, false) +} + +// Decrypt is the inverse of Encrypt. +func Decrypt(volumeKey, ciphertext []byte) ([]byte, error) { + return inMemory(volumeKey, ciphertext, true) +} + +func inMemory(volumeKey, in []byte, decrypt bool) ([]byte, error) { + if len(in)%SectorSize != 0 { + return nil, fmt.Errorf("data length %d is not a multiple of sector size %d", len(in), SectorSize) + } + c, err := newCipher(volumeKey) + if err != nil { + return nil, err + } + out := make([]byte, len(in)) + copy(out, in) + transform(c, out, 0, decrypt) + return out, nil +} + +// EncryptStream reads plaintext from src and writes ciphertext to dst, +// encrypting in sector-aligned chunks so large artifacts never load fully +// into memory. It returns the number of ciphertext bytes written (always a +// multiple of SectorSize). +// +// A trailing partial sector is zero-padded. In practice MWP images are always +// sector-aligned (the EROFS image and the dm-verity hash tree are both whole +// multiples of the 4096-byte block), so real artifacts encrypt with no +// padding and the padding path is purely defensive. +func EncryptStream(volumeKey []byte, dst io.Writer, src io.Reader) (int64, error) { + return stream(volumeKey, dst, src, false) +} + +// DecryptStream is the inverse of EncryptStream. Its input must be +// sector-aligned (ciphertext always is); a trailing partial sector is an +// error rather than being padded. +func DecryptStream(volumeKey []byte, dst io.Writer, src io.Reader) (int64, error) { + return stream(volumeKey, dst, src, true) +} + +func stream(volumeKey []byte, dst io.Writer, src io.Reader, decrypt bool) (int64, error) { + c, err := newCipher(volumeKey) + if err != nil { + return 0, err + } + buf := make([]byte, streamChunkBytes) + var baseUnit uint64 + var written int64 + for { + n, readErr := io.ReadFull(src, buf) + + // A short read from io.ReadFull means either end of stream (io.EOF or + // io.ErrUnexpectedEOF) or a genuine read error. Only end of stream + // justifies padding a trailing partial sector; on a real error we must + // not emit that (incorrectly padded) ciphertext, so surface the error + // before writing anything. + eof := readErr == io.EOF || readErr == io.ErrUnexpectedEOF + if readErr != nil && !eof { + return written, readErr + } + + if n > 0 { + full := n + if rem := n % SectorSize; rem != 0 { + if decrypt { + return written, fmt.Errorf("ciphertext is not sector-aligned (trailing %d bytes)", rem) + } + // A partial sector here implies clean EOF, so the tail is the + // real end of the data: zero-pad it to a full sector. + full = n - rem + SectorSize + clear(buf[n:full]) + } + transform(c, buf[:full], baseUnit, decrypt) + if _, err := dst.Write(buf[:full]); err != nil { + return written, err + } + baseUnit += uint64(full / SectorSize) + written += int64(full) + } + + if eof { + return written, nil + } + } +} diff --git a/crypt/crypt_test.go b/crypt/crypt_test.go new file mode 100644 index 0000000..b700a3c --- /dev/null +++ b/crypt/crypt_test.go @@ -0,0 +1,177 @@ +package crypt + +import ( + "bytes" + "errors" + "os" + "path/filepath" + "testing" +) + +// TestDmcryptGolden is the authoritative compatibility check: it asserts +// crypt.Encrypt reproduces, byte for byte, the ciphertext that the real +// cryptsetup produced for the same key and plaintext with the packer's exact +// flags (see testdata/gen-golden.sh). Passing this proves the kernel +// dm-crypt consumer will decrypt artifacts this package encrypts, and pins +// the one dm-crypt specific convention (the per-sector IV) without needing +// cryptsetup at test time. +func TestDmcryptGolden(t *testing.T) { + key := readTestdata(t, "key.bin") + pt := readTestdata(t, "plaintext.bin") + ct := readTestdata(t, "ciphertext.bin") + + got, err := Encrypt(key, pt) + if err != nil { + t.Fatalf("Encrypt: %v", err) + } + if !bytes.Equal(got, ct) { + t.Fatalf("ciphertext does not match dm-crypt golden vector;\n"+ + "the per-sector IV convention is likely wrong (try ivSectorRatio=1).\n"+ + "first mismatch at byte %d", firstDiff(got, ct)) + } + + back, err := Decrypt(key, ct) + if err != nil { + t.Fatalf("Decrypt: %v", err) + } + if !bytes.Equal(back, pt) { + t.Fatal("Decrypt(golden ciphertext) != plaintext") + } +} + +// TestDmcryptGoldenStream checks the streaming path produces the same golden +// ciphertext and round-trips, including across chunk boundaries. +func TestDmcryptGoldenStream(t *testing.T) { + key := readTestdata(t, "key.bin") + pt := readTestdata(t, "plaintext.bin") + ct := readTestdata(t, "ciphertext.bin") + + var enc bytes.Buffer + if _, err := EncryptStream(key, &enc, bytes.NewReader(pt)); err != nil { + t.Fatalf("EncryptStream: %v", err) + } + if !bytes.Equal(enc.Bytes(), ct) { + t.Fatalf("streamed ciphertext != golden (first mismatch at %d)", firstDiff(enc.Bytes(), ct)) + } + + var dec bytes.Buffer + if _, err := DecryptStream(key, &dec, bytes.NewReader(ct)); err != nil { + t.Fatalf("DecryptStream: %v", err) + } + if !bytes.Equal(dec.Bytes(), pt) { + t.Fatal("DecryptStream(golden) != plaintext") + } +} + +// TestStreamPadsTrailingSector confirms a non-sector-aligned plaintext is +// zero-padded on encrypt and recovered (with padding) on decrypt, matching +// the old backing-file behavior. +func TestStreamPadsTrailingSector(t *testing.T) { + key := bytes.Repeat([]byte{0x5A}, 64) + pt := bytes.Repeat([]byte{0xEE}, 2*SectorSize+123) // unaligned + + var enc bytes.Buffer + n, err := EncryptStream(key, &enc, bytes.NewReader(pt)) + if err != nil { + t.Fatalf("EncryptStream: %v", err) + } + if n != 3*SectorSize || enc.Len() != 3*SectorSize { + t.Fatalf("padded ciphertext = %d bytes, want %d", enc.Len(), 3*SectorSize) + } + + var dec bytes.Buffer + if _, err := DecryptStream(key, &dec, &enc); err != nil { + t.Fatalf("DecryptStream: %v", err) + } + if !bytes.Equal(dec.Bytes()[:len(pt)], pt) { + t.Fatal("decrypted prefix != original plaintext") + } + for i := len(pt); i < dec.Len(); i++ { + if dec.Bytes()[i] != 0 { + t.Fatalf("pad byte at %d = %d, want 0", i, dec.Bytes()[i]) + } + } +} + +// TestIdenticalSectorsDistinctCiphertext confirms the per-sector IV is +// actually applied: identical plaintext sectors at different offsets must +// encrypt to different ciphertext. testdata's sector 5 duplicates sector 0. +func TestIdenticalSectorsDistinctCiphertext(t *testing.T) { + key := readTestdata(t, "key.bin") + pt := readTestdata(t, "plaintext.bin") + if !bytes.Equal(pt[0:SectorSize], pt[5*SectorSize:6*SectorSize]) { + t.Skip("testdata sector 5 no longer duplicates sector 0") + } + ct, err := Encrypt(key, pt) + if err != nil { + t.Fatalf("Encrypt: %v", err) + } + if bytes.Equal(ct[0:SectorSize], ct[5*SectorSize:6*SectorSize]) { + t.Fatal("identical plaintext sectors produced identical ciphertext; IV not per-sector") + } +} + +func TestRejectsBadInput(t *testing.T) { + good := bytes.Repeat([]byte{1}, 64) + if _, err := Encrypt(good[:32], make([]byte, SectorSize)); err == nil { + t.Fatal("expected error for short key") + } + if _, err := Encrypt(good, make([]byte, SectorSize+1)); err == nil { + t.Fatal("expected error for non-sector-multiple length") + } + if _, err := DecryptStream(good, &bytes.Buffer{}, bytes.NewReader(make([]byte, SectorSize+1))); err == nil { + t.Fatal("expected error decrypting non-sector-aligned ciphertext") + } +} + +// errReader yields data once together with a non-EOF error, mimicking a +// reader that fails mid-stream after a partial, non-sector-aligned read. +type errReader struct { + data []byte + err error + done bool +} + +func (r *errReader) Read(p []byte) (int, error) { + if r.done { + return 0, r.err + } + r.done = true + return copy(p, r.data), r.err +} + +// TestEncryptStreamReadErrorNoWrite guards against treating a failed partial +// read as end-of-stream: a genuine read error must surface without emitting +// any (zero-padded) ciphertext. +func TestEncryptStreamReadErrorNoWrite(t *testing.T) { + key := bytes.Repeat([]byte{0x11}, 64) + boom := errors.New("boom") + r := &errReader{data: bytes.Repeat([]byte{0xCD}, 100), err: boom} // 100 % SectorSize != 0 + + var dst bytes.Buffer + n, err := EncryptStream(key, &dst, r) + if !errors.Is(err, boom) { + t.Fatalf("err = %v, want boom", err) + } + if n != 0 || dst.Len() != 0 { + t.Fatalf("wrote %d bytes (reported %d) despite read error; want none", dst.Len(), n) + } +} + +func readTestdata(t *testing.T, name string) []byte { + t.Helper() + b, err := os.ReadFile(filepath.Join("testdata", name)) + if err != nil { + t.Fatalf("reading testdata/%s: %v", name, err) + } + return b +} + +func firstDiff(a, b []byte) int { + for i := 0; i < len(a) && i < len(b); i++ { + if a[i] != b[i] { + return i + } + } + return min(len(a), len(b)) +} diff --git a/crypt/testdata/ciphertext.bin b/crypt/testdata/ciphertext.bin new file mode 100644 index 0000000..eea2222 Binary files /dev/null and b/crypt/testdata/ciphertext.bin differ diff --git a/crypt/testdata/gen-golden.sh b/crypt/testdata/gen-golden.sh new file mode 100755 index 0000000..a06b787 --- /dev/null +++ b/crypt/testdata/gen-golden.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Regenerate the dm-crypt golden vector used by TestDmcryptGolden: +# +# key.bin 64-byte volume key (bytes 0x00..0x3f). +# plaintext.bin Six 4096-byte sectors with distinct fill bytes; sector 5 +# repeats sector 0 so the vector exercises identical +# plaintext at distinct IVs. +# ciphertext.bin plaintext.bin encrypted by the real cryptsetup with the +# exact flags the packer uses (aes-xts-plain64, 512-bit key, +# 4096-byte sectors, skip 0). +# +# crypt.Encrypt must reproduce ciphertext.bin byte for byte, which pins the +# packer's native-Go XTS to kernel dm-crypt. Everything runs inside one +# privileged container, so the only host requirement is Docker: +# +# ./crypt/testdata/gen-golden.sh +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +docker run --rm --privileged -i -v "$PWD:/td" debian:trixie-slim bash -s <<'SCRIPT' +set -euo pipefail +S=4096 + +# emit_sector N writes S copies of the byte value N to stdout. +emit_sector() { printf "\\x$(printf '%02x' "$1")%.0s" $(seq "$S"); } + +# key.bin: the 64 bytes 0x00..0x3f. +for i in $(seq 0 63); do printf "\\x$(printf '%02x' "$i")"; done > /td/key.bin + +# plaintext.bin: sectors filled with 0xA0..0xA4, then a sixth repeating 0xA0. +{ for v in 160 161 162 163 164; do emit_sector "$v"; done; emit_sector 160; } > /td/plaintext.bin + +# ciphertext.bin: encrypt with the real cryptsetup, matching the packer flags. +# The cryptsetup version is not pinned: dm-crypt aes-xts-plain64 output is +# kernel/format-determined and stable across cryptsetup versions. +apt-get update -qq && apt-get install -y -qq cryptsetup-bin >/dev/null +cp /td/plaintext.bin /work_ct +cryptsetup open --type plain --cipher aes-xts-plain64 --key-size 512 \ + --sector-size 4096 --key-file /td/key.bin --skip 0 /work_ct golden +dd if=/td/plaintext.bin of=/dev/mapper/golden bs=4096 conv=notrunc status=none +sync +cryptsetup close golden +cp /work_ct /td/ciphertext.bin +SCRIPT + +echo "wrote key.bin ($(wc -c < key.bin)B), plaintext.bin ($(wc -c < plaintext.bin)B), ciphertext.bin ($(wc -c < ciphertext.bin)B)" diff --git a/crypt/testdata/key.bin b/crypt/testdata/key.bin new file mode 100644 index 0000000..96eb299 Binary files /dev/null and b/crypt/testdata/key.bin differ diff --git a/crypt/testdata/plaintext.bin b/crypt/testdata/plaintext.bin new file mode 100644 index 0000000..1737b58 --- /dev/null +++ b/crypt/testdata/plaintext.bin @@ -0,0 +1 @@ +                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                ĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦĦ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££££¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 \ No newline at end of file diff --git a/go.mod b/go.mod index 0cefaa8..8af1e2c 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,9 @@ module github.com/tinfoilsh/modelwrap -go 1.25 +go 1.25.0 require github.com/google/uuid v1.6.0 require golang.org/x/mod v0.31.0 + +require golang.org/x/crypto v0.52.0 diff --git a/go.sum b/go.sum index 5d7866a..258723a 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= diff --git a/integration_test.go b/integration_test.go index 4f69f7b..6e4b7c5 100644 --- a/integration_test.go +++ b/integration_test.go @@ -5,6 +5,7 @@ package modelwrap_test import ( "encoding/base64" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -114,3 +115,152 @@ func TestEMWPRoundTripIntegration(t *testing.T) { } } } + +// TestEMWPGoEncryptKernelDecrypt is the authoritative compatibility test for +// the native-Go dm-crypt encryption: it packs a local directory as EMWP +// (encrypted entirely in userspace by package crypt, no cryptsetup) and then +// decrypts it with the real kernel dm-crypt via cryptsetup, verifies it with +// kernel dm-verity, and mounts it. If the Go ciphertext were not byte-exact +// dm-crypt output, the kernel would decrypt garbage and dm-verity would fail. +// It needs no network or huggingface tooling, only a privileged Linux +// environment with erofs-utils, cryptsetup, and gdisk. +func TestEMWPGoEncryptKernelDecrypt(t *testing.T) { + if os.Getenv("TINFOIL_MODELWRAP_INTEGRATION") != "1" { + t.Skip("set TINFOIL_MODELWRAP_INTEGRATION=1 to run") + } + + work := t.TempDir() + modelDir := filepath.Join(work, "model") + if err := os.MkdirAll(modelDir, 0755); err != nil { + t.Fatal(err) + } + // Content large enough to span many 4096-byte sectors, so the kernel + // must walk the per-sector IV across the whole partition to recover it. + modelFiles := map[string][]byte{ + "config.json": []byte(`{"model_type":"test","n":12345}`), + "pytorch_model.bin": func() []byte { + b := make([]byte, 512*1024) + for i := range b { + b[i] = byte(i*131 + 7) + } + return b + }(), + } + for name, content := range modelFiles { + if err := os.WriteFile(filepath.Join(modelDir, name), content, 0644); err != nil { + t.Fatal(err) + } + } + + masterKey := []byte(strings.Repeat("k", modelwrap.EMWPMasterKeyBytes)) + keyFile := filepath.Join(work, "master.key") + if err := os.WriteFile(keyFile, []byte(base64.StdEncoding.EncodeToString(masterKey)), 0600); err != nil { + t.Fatal(err) + } + + // Pack with the Go encryptor. Verify:false on purpose: the only + // verification that matters here is the kernel's, performed below. + rawRef, err := wrap.Pack(wrap.Options{ + Model: "testmodel@v1", + ModelDir: modelDir, + CacheDir: filepath.Join(work, "cache"), + OutputDir: filepath.Join(work, "output"), + Encrypt: true, + Verify: false, + KeyFile: keyFile, + }) + if err != nil { + t.Fatalf("packing EMWP: %v", err) + } + ref, err := modelwrap.ParseRef(rawRef) + if err != nil { + t.Fatalf("parsing packed ref %q: %v", rawRef, err) + } + + emwpFile := filepath.Join(work, "output", "testmodel", "v1.emwp") + fi, err := os.Stat(emwpFile) + if err != nil { + t.Fatal(err) + } + + // Expose the encrypted payload partition exactly as the consumer does. + partOffset := int64(modelwrap.EMWPPartitionStartSector * modelwrap.GPTSectorSize) + partSize := fi.Size() - partOffset - int64(modelwrap.EMWPGPTTrailingSectors*modelwrap.GPTSectorSize) + out, err := exec.Command( + "losetup", "--read-only", "--find", "--show", + "--offset", fmt.Sprint(partOffset), + "--sizelimit", fmt.Sprint(partSize), + emwpFile, + ).Output() + if err != nil { + t.Fatalf("losetup: %v", err) + } + loopDev := strings.TrimSpace(string(out)) + t.Cleanup(func() { _ = exec.Command("losetup", "-d", loopDev).Run() }) + + dmKey, err := modelwrap.DeriveKey(masterKey, ref) + if err != nil { + t.Fatalf("deriving dm-crypt key: %v", err) + } + dmKeyFile := filepath.Join(work, "dm.key") + if err := os.WriteFile(dmKeyFile, dmKey, 0600); err != nil { + t.Fatal(err) + } + + // Kernel dm-crypt decrypt of the Go-produced ciphertext. If the Go XTS + // output were not byte-exact dm-crypt ciphertext, the kernel would + // produce different plaintext through this mapping. + cryptName := "modelwrap-goit-crypt" + if err := unwrap.OpenCrypt(loopDev, cryptName, dmKeyFile); err != nil { + t.Fatalf("kernel cryptsetup open of Go ciphertext: %v", err) + } + t.Cleanup(func() { unwrap.CloseCrypt(cryptName) }) + + // Authoritative check: the bytes the kernel dm-crypt produces must equal + // the original MWP plaintext the packer encrypted in Go. + plain, err := os.ReadFile(filepath.Join(work, "output", "testmodel", "v1.mpk")) + if err != nil { + t.Fatal(err) + } + dev, err := os.Open("/dev/mapper/" + cryptName) + if err != nil { + t.Fatal(err) + } + defer dev.Close() + got := make([]byte, len(plain)) + if _, err := io.ReadFull(dev, got); err != nil { + t.Fatalf("reading kernel-decrypted device: %v", err) + } + if !bytesEqual(got, plain) { + t.Fatalf("kernel dm-crypt decryption does not match the original MWP plaintext "+ + "(first diff at byte %d): Go ciphertext is not dm-crypt compatible", firstDiffIdx(got, plain)) + } + + // Independent integrity check, in userspace so it needs no dm-verity + // kernel target: the kernel-decrypted plaintext must satisfy the attested + // root hash. + if err := wrap.VerifyMWP("/dev/mapper/"+cryptName, filepath.Join(work, "output", "testmodel", "v1.info")); err != nil { + t.Fatalf("veritysetup verify over kernel-decrypted plaintext: %v", err) + } +} + +func firstDiffIdx(a, b []byte) int { + for i := 0; i < len(a) && i < len(b); i++ { + if a[i] != b[i] { + return i + } + } + return min(len(a), len(b)) +} + +func bytesEqual(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/test/e2e.sh b/test/e2e.sh index cacabdb..7535f00 100755 --- a/test/e2e.sh +++ b/test/e2e.sh @@ -44,10 +44,11 @@ docker run --rm --privileged \ -test.run TestEMWPRoundTripIntegration -test.v # CLI smoke test: the user-facing entrypoint with volumes and key file. +# Packing is userspace-only, so it runs unprivileged. mkdir -p "${WORK_DIR}/cache" "${WORK_DIR}/output" printf '%s\n' "${KEY_B64}" > "${WORK_DIR}/emwp-key" -docker run --rm --privileged \ +docker run --rm \ -v "${WORK_DIR}/cache:/cache" \ -v "${WORK_DIR}/emwp-key:/run/emwp-key:ro" \ -v "${WORK_DIR}/output:/output" \ diff --git a/wrap/wrap.go b/wrap/wrap.go index b1db99d..b429c85 100644 --- a/wrap/wrap.go +++ b/wrap/wrap.go @@ -17,10 +17,10 @@ import ( "os" "os/exec" "path/filepath" - "strconv" "strings" "github.com/tinfoilsh/modelwrap" + "github.com/tinfoilsh/modelwrap/crypt" ) // Options configures a single packing run. Model is a Hugging Face model @@ -318,24 +318,17 @@ func encryptEMWP(mwpFile, emwpFile string, ref *modelwrap.ArtifactRef, masterKey totalSectors := endSector + 1 + modelwrap.EMWPGPTTrailingSectors diskUUID := modelwrap.UUIDv5URL(ref.RootHash + "-emwp-disk") tmpFile := emwpFile + ".tmp" - dmKeyFile := emwpFile + ".key.tmp" - mapperName := "modelwrap-emwp-" + ref.RootHash[:16] dmKey, err := modelwrap.DeriveKey(masterKey, ref) if err != nil { return err } + defer clear(dmKey) - for _, path := range []string{tmpFile, dmKeyFile} { - if err := os.Remove(path); err != nil && !os.IsNotExist(err) { - return err - } + if err := os.Remove(tmpFile); err != nil && !os.IsNotExist(err) { + return err } - defer func() { - closeCryptMapper(mapperName) - os.Remove(dmKeyFile) - os.Remove(tmpFile) - }() + defer os.Remove(tmpFile) fmt.Printf("Creating EMWP GPT image %s\n", emwpFile) if err := createSparseFile(tmpFile, totalSectors*modelwrap.GPTSectorSize); err != nil { @@ -355,33 +348,31 @@ func encryptEMWP(mwpFile, emwpFile string, ref *modelwrap.ArtifactRef, masterKey return err } - if err := os.WriteFile(dmKeyFile, dmKey, 0600); err != nil { + if err := encryptPayload(tmpFile, mwpFile, dmKey); err != nil { return err } - err = run(exec.Command( - "cryptsetup", "open", - "--type", "plain", - "--cipher", modelwrap.EMWPCipher, - "--key-size", strconv.Itoa(modelwrap.EMWPKeySizeBits), - "--sector-size", strconv.Itoa(modelwrap.EMWPSectorSize), - "--key-file", dmKeyFile, - "--offset", strconv.Itoa(modelwrap.EMWPPartitionStartSector), - "--skip", "0", - "--size", strconv.FormatInt(sectors, 10), - tmpFile, - mapperName, - )) + return os.Rename(tmpFile, emwpFile) +} + +// encryptPayload streams the dm-crypt encryption of mwpFile into the +// encrypted payload partition of the GPT image at imgFile. +func encryptPayload(imgFile, mwpFile string, dmKey []byte) error { + in, err := os.Open(mwpFile) if err != nil { return err } - - if err := copyToDevice(mwpFile, "/dev/mapper/"+mapperName); err != nil { + defer in.Close() + out, err := os.OpenFile(imgFile, os.O_RDWR, 0) + if err != nil { return err } - if err := closeCryptMapper(mapperName); err != nil { - return err + defer out.Close() + + payload := io.NewOffsetWriter(out, modelwrap.EMWPPartitionStartSector*modelwrap.GPTSectorSize) + if _, err := crypt.EncryptStream(dmKey, payload, in); err != nil { + return fmt.Errorf("encrypting %s: %w", mwpFile, err) } - return os.Rename(tmpFile, emwpFile) + return out.Sync() } // VerifyMWP runs an offline dm-verity verification of an MWP artifact @@ -415,8 +406,8 @@ func VerifyMWP(mwpFile, infoFile string) error { return nil } -// VerifyEMWP decrypts an EMWP artifact through a temporary dm-crypt -// mapping and verifies the inner dm-verity tree. +// VerifyEMWP decrypts an EMWP artifact's payload partition in userspace and +// verifies the inner dm-verity tree of the recovered MWP plaintext. func VerifyEMWP(emwpFile, infoFile string, masterKey []byte) error { ref, err := parseInfoFile(infoFile) if err != nil { @@ -427,38 +418,42 @@ func VerifyEMWP(emwpFile, infoFile string, masterKey []byte) error { return fmt.Errorf("EMWP artifact not found: %s", emwpFile) } - sectors := fi.Size()/modelwrap.GPTSectorSize - modelwrap.EMWPPartitionStartSector - modelwrap.EMWPGPTTrailingSectors - dmKeyFile := emwpFile + ".key.tmp" - mapperName := "modelwrap-emwp-verify-" + ref.RootHash[:16] dmKey, err := modelwrap.DeriveKey(masterKey, ref) if err != nil { return err } + defer clear(dmKey) - defer func() { - closeCryptMapper(mapperName) - os.Remove(dmKeyFile) - }() - if err := os.WriteFile(dmKeyFile, dmKey, 0600); err != nil { + plainFile := emwpFile + ".verify.tmp" + defer os.Remove(plainFile) + if err := decryptPayload(emwpFile, plainFile, fi.Size(), dmKey); err != nil { return err } - err = run(exec.Command( - "cryptsetup", "open", - "--type", "plain", - "--cipher", modelwrap.EMWPCipher, - "--key-size", strconv.Itoa(modelwrap.EMWPKeySizeBits), - "--sector-size", strconv.Itoa(modelwrap.EMWPSectorSize), - "--key-file", dmKeyFile, - "--offset", strconv.Itoa(modelwrap.EMWPPartitionStartSector), - "--skip", "0", - "--size", strconv.FormatInt(sectors, 10), - emwpFile, - mapperName, - )) + return VerifyMWP(plainFile, infoFile) +} + +// decryptPayload streams the decrypted MWP plaintext of an EMWP image's +// payload partition into plainFile. +func decryptPayload(emwpFile, plainFile string, emwpSize int64, dmKey []byte) error { + partOffset := int64(modelwrap.EMWPPartitionStartSector * modelwrap.GPTSectorSize) + partSize := emwpSize - partOffset - int64(modelwrap.EMWPGPTTrailingSectors*modelwrap.GPTSectorSize) + + in, err := os.Open(emwpFile) if err != nil { return err } - return VerifyMWP("/dev/mapper/"+mapperName, infoFile) + defer in.Close() + out, err := os.OpenFile(plainFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer out.Close() + + payload := io.NewSectionReader(in, partOffset, partSize) + if _, err := crypt.DecryptStream(dmKey, out, payload); err != nil { + return fmt.Errorf("decrypting EMWP payload: %w", err) + } + return out.Sync() } // LoadMasterKey loads the EMWP master key from keyFile if set, else from @@ -504,31 +499,6 @@ func createSparseFile(path string, size int64) error { return f.Truncate(size) } -// copyToDevice writes src into the block device dst and syncs it. -func copyToDevice(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - out, err := os.OpenFile(dst, os.O_WRONLY, 0) - if err != nil { - return err - } - defer out.Close() - if _, err := io.CopyBuffer(out, in, make([]byte, 4*1024*1024)); err != nil { - return fmt.Errorf("writing %s to %s: %w", src, dst, err) - } - return out.Sync() -} - -func closeCryptMapper(name string) error { - if _, err := os.Stat("/dev/mapper/" + name); os.IsNotExist(err) { - return nil - } - return run(exec.Command("cryptsetup", "close", name)) -} - func run(cmd *exec.Cmd) error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr